ConfigMaps, Secrets, and Storage
Learn how Kubernetes separates configuration from images using ConfigMaps/Secrets and how volumes persist data.
ConfigMaps
A ConfigMap stores non-sensitive configuration as key-value pairs, keeping config out of your container images.
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: "production"
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
config.json: |
{
"featureFlags": {
"darkMode": true,
"betaApi": false
}
}
kubectl apply -f configmap.yaml
# View the configmap
kubectl get configmap app-config
kubectl describe configmap app-config
# Create a configmap directly from a file
kubectl create configmap nginx-conf --from-file=nginx.conf
# Create from literal values
kubectl create configmap db-config --from-literal=DB_HOST=postgres --from-literal=DB_PORT=5432
Using ConfigMap as Environment Variables
spec:
containers:
- name: app
image: my-api:1.0
envFrom:
- configMapRef:
name: app-config # inject all keys as env vars
env:
- name: SPECIFIC_KEY # inject a single key
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
Using ConfigMap as a Mounted File
spec:
volumes:
- name: config-vol
configMap:
name: app-config
containers:
- name: app
image: my-api:1.0
volumeMounts:
- name: config-vol
mountPath: /etc/config # each key becomes a file
# Inside the container, files appear at /etc/config/APP_ENV, etc.
kubectl exec -it <pod-name> -- ls /etc/config
Secrets
A Secret stores sensitive data (passwords, tokens, certificates) encoded in base64.
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
DB_PASSWORD: cGFzc3dvcmQxMjM= # base64("password123")
API_KEY: c3VwZXJzZWNyZXQ= # base64("supersecret")
# Encode a value to base64
echo -n "password123" | base64
# Decode a secret value
kubectl get secret db-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 --decode
# Create a secret imperatively (avoids storing plaintext in YAML)
kubectl create secret generic db-secret \
--from-literal=DB_PASSWORD=password123 \
--from-literal=API_KEY=supersecret
# Create a TLS secret from cert files
kubectl create secret tls my-tls \
--cert=tls.crt \
--key=tls.key
Using Secrets in Pods
spec:
containers:
- name: app
image: my-api:1.0
envFrom:
- secretRef:
name: db-secret # inject all secret keys as env vars
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASSWORD
Volumes and Persistent Storage
emptyDir — Temporary Shared Storage
Lives as long as the Pod exists. Great for sharing files between sidecar containers.
spec:
volumes:
- name: scratch
emptyDir: {}
containers:
- name: writer
image: busybox
command: ["sh", "-c", "echo hello > /data/msg; sleep 3600"]
volumeMounts:
- name: scratch
mountPath: /data
- name: reader
image: busybox
command: ["sh", "-c", "cat /data/msg; sleep 3600"]
volumeMounts:
- name: scratch
mountPath: /data
PersistentVolume and PersistentVolumeClaim
For data that must survive Pod restarts or rescheduling.
# pvc.yaml — request storage (you don't manage where it comes from)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce # one node can read/write at a time
resources:
requests:
storage: 5Gi
storageClassName: standard # use "standard" for minikube
# Use the PVC in a Deployment
spec:
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc # reference the PVC
containers:
- name: postgres
image: postgres:15
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASSWORD
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
kubectl apply -f pvc.yaml
# Check PVC status (Bound = storage allocated)
kubectl get pvc
kubectl describe pvc postgres-pvc
# Check the underlying PersistentVolume
kubectl get pv
Access Modes
| Mode | Abbreviation | Meaning |
|---|---|---|
ReadWriteOnce | RWO | Single node read/write |
ReadOnlyMany | ROX | Multiple nodes read-only |
ReadWriteMany | RWX | Multiple nodes read/write |
Complete Database Deployment Example
# postgres-full.yaml
---
apiVersion: v1
kind: Secret
metadata:
name: pg-secret
data:
POSTGRES_PASSWORD: cGFzc3dvcmQ= # "password"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: pg-config
data:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pg-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: pg-pvc
containers:
- name: postgres
image: postgres:15
envFrom:
- configMapRef:
name: pg-config
- secretRef:
name: pg-secret
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
ports:
- containerPort: 5432
kubectl apply -f postgres-full.yaml
kubectl get pods,pvc,svc
Learning Outcomes
You can:
- Create ConfigMaps and consume them as env vars or mounted files
- Store sensitive data in Secrets and reference them securely in Pods
- Choose the right volume type (emptyDir vs PVC) for your use case
- Write a complete stateful workload with config, secrets, and persistent storage
Frequently Asked Questions
When should I use a ConfigMap vs a Secret?
Use ConfigMaps for non-sensitive configuration (app settings, feature flags). Use Secrets for sensitive data (passwords, tokens, certificates). Secrets are base64-encoded by default — add encryption-at-rest for production.
What happens to data in an emptyDir volume when a pod restarts?
emptyDir data survives container restarts within the same pod, but is deleted when the pod is removed from a node. Use a PersistentVolume for data that must survive pod deletion.