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.