ConfigMaps, Secrets, and Volumes
Hardcoding configuration (URLs, feature flags, credentials) directly into a container image means rebuilding the image every time a setting changes, and it means secrets end up baked into an image anyone with registry access can pull. Kubernetes externalizes this with ConfigMaps, Secrets, and Volumes.
ConfigMaps: non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
data:
LOG_LEVEL: 'info'
FEATURE_NEW_CHECKOUT: 'true'
# inside a Pod spec
containers:
- name: web
image: myregistry/web:1.5.0
envFrom:
- configMapRef:
name: web-config
envFrom/configMapRef injects every key in the ConfigMap as an environment variable, change the ConfigMap and restart the Pods, no image rebuild required.
Secrets: the same idea, for sensitive values
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
password: cGFzc3dvcmQxMjM= # base64-encoded, NOT encrypted
WARNING
Secret values are base64-encoded, not encrypted, by default, anyone who can read the Secret object can trivially decode it. Real production clusters pair Secrets with encryption-at-rest and tools like a proper secrets manager (Vault, AWS Secrets Manager, sealed-secrets) rather than relying on the base64 encoding for actual protection.
Volumes: storage that outlives a container
A container's own filesystem disappears the moment it restarts. A Volume attaches storage to a Pod that can persist across container restarts (or even be shared between containers in the same Pod).
spec:
containers:
- name: web
image: myregistry/web:1.5.0
volumeMounts:
- name: cache-data
mountPath: /var/cache/app
volumes:
- name: cache-data
emptyDir: {}
emptyDir survives container restarts within the same Pod, but is deleted when the Pod itself is removed. For storage that must outlive the Pod entirely (a database's data directory), you'd use a PersistentVolumeClaim instead, which requests storage from the cluster that exists independently of any one Pod's lifecycle.