Skip to main content
PProfileMatch

Kubernetes ConfigMaps, Secrets and Persistent Volumes (PVC)

DevOpsLesson 3 of 45 min read

What you'll learn

  • Inject configuration into Pods through environment variables and mounted files
  • Create and consume a Secret and explain its real security limits
  • Explain PersistentVolume, PersistentVolumeClaim and StorageClass
  • Choose between a Deployment and a StatefulSet for stateful workloads

On this page

  1. ConfigMaps: non-sensitive settings
  2. Secrets: sensitive values, with an important caveat
  3. Wiring both into a Deployment
  4. Persistent storage: PV, PVC and StorageClass
  5. Common mistakes and the symptoms they cause
  6. How this shows up in real projects and interviews

A greenhouse company runs a collector that receives readings from soil sensors. The same container image must run in a test cluster that logs verbosely and discards data, and in production where it writes real readings and talks to a real database. Rebuilding the image per environment would defeat the point of images. Kubernetes gives you separate objects for configuration, sensitive values and storage, and attaches them to Pods at run time.

ConfigMaps: non-sensitive settings

A ConfigMap stores key-value pairs or whole file contents. A Pod can consume it as environment variables or as files in a mounted directory. The example below defines two simple settings and a small config file for the collector.

collector-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: collector-config
data:
  LOG_LEVEL: "info"
  BATCH_SIZE: "200"
  thresholds.toml: |
    [moisture]
    min = 18
    max = 62

    [temperature]
    max_c = 38

Every value in a ConfigMap is a string, which is why BATCH_SIZE is quoted: YAML would otherwise parse it as a number and the API would reject it. The | after thresholds.toml: starts a block scalar so the multi-line file is stored verbatim.

Secrets: sensitive values, with an important caveat

A Secret has the same shape as a ConfigMap but is intended for passwords, tokens and keys. Kubernetes treats it more carefully: it can be restricted with RBAC, kept out of some logs and kept in memory-backed files on the node. You can write values as plain text under stringData and the API server stores them encoded.

Terminal
# Create a Secret without writing it to a file
kubectl create secret generic collector-db \
  --from-literal=DB_PASSWORD=change-me-local-only

# Read it back the way anyone with read access can
kubectl get secret collector-db -o jsonpath='{.data.DB_PASSWORD}'
echo
kubectl get secret collector-db -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

Output

Y2hhbmdlLW1lLWxvY2FsLW9ubHk=
change-me-local-only

Wiring both into a Deployment

collector-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sensor-collector
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sensor-collector
  template:
    metadata:
      labels:
        app: sensor-collector
    spec:
      containers:
        - name: collector
          image: ghcr.io/example-org/sensor-collector:2.3.0
          env:
            - name: LOG_LEVEL
              valueFrom:
                configMapKeyRef:
                  name: collector-config
                  key: LOG_LEVEL
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: collector-db
                  key: DB_PASSWORD
          volumeMounts:
            - name: settings
              mountPath: /etc/collector
              readOnly: true
            - name: readings
              mountPath: /var/lib/collector
      volumes:
        - name: settings
          configMap:
            name: collector-config
            items:
              - key: thresholds.toml
                path: thresholds.toml
        - name: readings
          persistentVolumeClaim:
            claimName: readings-pvc

Environment variables are read once when the container starts, so changing the ConfigMap does not update a running Pod's variables; you must restart the Pods. Files mounted from a ConfigMap are refreshed on disk eventually after the ConfigMap changes (not for mounts that use subPath), but your application only sees new values if it re-reads the file. For the Secret, prefer a mounted file over an environment variable when the application supports it, since environment variables are easy to leak through crash dumps and debugging endpoints.

Persistent storage: PV, PVC and StorageClass

Pod filesystems are disposable, so durable data needs a volume that outlives the Pod. Kubernetes splits this into three ideas. A PersistentVolume (PV) is a piece of real storage in the cluster. A PersistentVolumeClaim (PVC) is an application's request for storage of some size and access mode. A StorageClass describes a kind of storage (fast SSD, cheap disk) and lets the cluster create PVs on demand. With dynamic provisioning you only write the claim.

readings-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: readings-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

Because no storageClassName is given, the cluster's default class is used if one exists. ReadWriteOnce means the volume can be mounted read-write by one node at a time. That detail has consequences: a Deployment with several replicas spread over nodes cannot all mount the same ReadWriteOnce volume. Check with kubectl get pvc; a claim stuck in Pending usually means no matching StorageClass or no capacity.

WorkloadUseWhy
Stateless API or workerDeploymentReplicas are interchangeable; any Pod can be replaced
Single-instance app with one volumeDeployment with replicas: 1 (strategy Recreate)Avoids two Pods fighting over one ReadWriteOnce volume during rollout
Database, queue or cache needing stable identityStatefulSetStable Pod names, ordered startup and one PVC per replica via volumeClaimTemplates
Managed cloud databaseNot in the cluster at allBackups, failover and upgrades are handled for you

Common mistakes and the symptoms they cause

  • Referencing a ConfigMap or Secret that does not exist. Symptom: Pod stuck in CreateContainerConfigError, with an event saying the configmap or secret was not found. Fix: create it first, in the same namespace.
  • Expecting env vars to update live. Symptom: you edited the ConfigMap but the app still uses old values. Fix: restart with kubectl rollout restart deployment/<name>.
  • Unquoted numbers and booleans in a ConfigMap. Symptom: an error that a value of type number or boolean cannot be unmarshalled into a string. Fix: quote the value.
  • Double-encoding a Secret. Symptom: the app receives a base64 string instead of the password. It happens when you base64-encode a value and then put it under stringData. Fix: use data for encoded values and stringData for plain ones.
  • PVC stuck in `Pending`. Symptom: the Pod stays Pending with an event that it has unbound immediate PersistentVolumeClaims. Fix: check kubectl describe pvc and the StorageClass.
  • Committing Secret YAML to Git. Symptom: credentials are visible to everyone with repository access, forever. Fix: rotate them and use sealed or external secret tooling.

How this shows up in real projects and interviews

A favourite question is "are Kubernetes Secrets secure?" The honest answer is "more than ConfigMaps, but only if you configure access and encryption at rest". Another is "how do you run a database on Kubernetes?", where a good candidate mentions StatefulSets, PVCs, backups, and the legitimate alternative of using a managed database. On the job, the most common config bug is a Pod that cannot start because a Secret lives in a different namespace than the Pod. The same configuration habits appear in the Docker course as environment variables and mounts, just with an API object in between.

Try it yourself

Create a ConfigMap with one setting and one small file, plus a Secret with one value. Mount both into a Pod running a busybox container with a sleep command, then use kubectl exec to print the environment variable and cat the file. Edit the ConfigMap and observe what changes without restarting.

Show a hint

Run kubectl exec <pod> -- sh -c 'env | grep LOG_LEVEL; cat /etc/collector/thresholds.toml'. Remember mounted files can take a short while to refresh.

Key takeaways

  • ConfigMaps hold non-sensitive settings; Secrets hold sensitive ones but are only base64-encoded unless the cluster adds encryption.
  • Env vars are fixed at container start; mounted files can refresh, but the app must re-read them.
  • A PVC requests storage, a PV supplies it, and a StorageClass lets the cluster provision it dynamically.
  • ReadWriteOnce volumes limit how replicas can be spread across nodes.
  • Use StatefulSets for workloads that need stable identity and per-replica storage, or use a managed service.

Test yourself

A trick from this lesson's ride: base64 is a costume, not a lock. Real protection is RBAC plus encryption at rest. Four more tricks like it wait along the route, each followed by a question.

Ride this lesson

Frequently asked questions

What is the difference between a ConfigMap and a Secret in Kubernetes?

Both store key-value data you can inject into Pods. ConfigMaps are for non-sensitive settings. Secrets are for sensitive data and get extra handling such as RBAC scoping, but they are only base64-encoded by default, so you still need encryption at rest and tight access control.

Is a Kubernetes Secret encrypted?

Not by default. The values are base64-encoded, which anyone with read access can decode, and they are stored unencrypted in etcd unless encryption at rest is enabled. Combine RBAC, encryption at rest and possibly an external secrets manager for strong protection.

What is the difference between a PV and a PVC in Kubernetes?

A PersistentVolume is an actual piece of storage available to the cluster. A PersistentVolumeClaim is a request for storage by a user or application, specifying size and access mode. Kubernetes binds a claim to a suitable volume, often one provisioned dynamically by a StorageClass.