K8s Storage and Configuration: PV/PVC, ConfigMap, Secret

Keep stateful data alive with PersistentVolumes, and feed configuration and secrets into your containers the safe way.

Containers are disposable by design: when a Pod is recreated, anything written to the container's filesystem is gone. The moment you run a database, an upload directory, or an app that needs external configuration on your own server / VPS cluster, you'll reach for Kubernetes storage and configuration objects. This article covers three of them: persistent storage (PV/PVC), configuration (ConfigMap), and sensitive data (Secret).

Persistent storage: PV and PVC

Kubernetes decouples providing storage from using it through two objects:

  • PersistentVolume (PV): a real piece of storage backed by a local disk, NFS, cloud block device, and so on — provisioned by an admin or a storage system.
  • PersistentVolumeClaim (PVC): a request from an app that says "I need this much space, with this access mode."

Dynamic provisioning with StorageClass

Creating PVs by hand is tedious. In practice you use a StorageClass for dynamic provisioning: you submit only a PVC, and the cluster creates a matching PV automatically.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  accessModes:
    - ReadWriteOnce        # single-node read/write, the common case
  resources:
    requests:
      storage: 10Gi
  storageClassName: standard   # swap in whatever StorageClass your cluster has

Run kubectl get storageclass to see what's available; omit storageClassName to use the cluster default. After applying, kubectl get pvc should show the status turn to Bound.

Mount it into a Pod:

spec:
  containers:
    - name: app
      image: myapp:latest
      volumeMounts:
        - name: data
          mountPath: /var/lib/app
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: data-pvc

Now everything under /var/lib/app outlives the Pod — recreate the Pod and the data is still there.

> Heads-up: ReadWriteOnce can be mounted read/write by a single node only. Writing from several nodes at once needs ReadWriteMany, and the underlying storage has to support it (NFS, for example). For stateful services, prefer a StatefulSet with volumeClaimTemplates.

Configuration: ConfigMap

A ConfigMap holds non-sensitive configuration: environment variables, config files, command-line flags.

Create one from the command line:

kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=info \
  --from-file=app.conf=./app.conf

There are two ways a Pod can consume it. As environment variables:

envFrom:
  - configMapRef:
      name: app-config

Or mounted as files, where each key becomes its own file:

volumeMounts:
  - name: cfg
    mountPath: /etc/app
volumes:
  - name: cfg
    configMap:
      name: app-config

Sensitive data: Secret

Passwords, API keys, and certificates belong in a Secret, not a ConfigMap. The mechanics are almost identical:

kubectl create secret generic db-secret \
  --from-literal=DB_PASSWORD='s3cr3t'

Inject it as an environment variable:

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-secret
        key: DB_PASSWORD

The one security caveat: a Secret is only base64

Plenty of people assume a Secret is "encrypted." By default it is just base64-encoded — anyone who can read the Secret decodes it back to plaintext in seconds. So you have to:

  • Lock it down with RBAC: grant get and list on Secrets only to the ServiceAccounts and users that genuinely need them.
  • Turn on encryption at rest: configure etcd encryption so Secrets are actually encrypted on disk.
  • Keep Secrets out of Git: a plaintext-or-base64 YAML in your repo is a leak. For GitOps, use Sealed Secrets, SOPS, or an external secrets manager such as Vault.
  • Prefer mounting as files over env vars: environment variables are easily inherited by child processes or dumped into logs.

Summary

  • Claim storage with a PVC, let a StorageClass dynamically provision the backing PV, and once it's mounted your data lives independently of any Pod.
  • Use a ConfigMap for non-sensitive settings and a Secret for sensitive ones; both can be injected as environment variables or mounted as files.
  • A Secret is base64, not encryption — pair it with least-privilege RBAC, etcd encryption at rest, and keep it out of Git. Get these three pieces right and your apps run stateful, configurable, and secure inside the cluster.