ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
FEATURE_FLAGS: "new-search,dark-mode"Consume it as environment variables, or mount it as files:
spec:
containers:
- name: app
image: docktionary/app:1.4.0
envFrom:
- configMapRef:
name: app-config
volumeMounts:
- name: config-files
mountPath: /etc/config
volumes:
- name: config-files
configMap:
name: app-configSecret
The same shape as a ConfigMap, for sensitive values. Values are base64-encoded in data — that is encoding, not encryption. Encryption at rest for etcd, or an external secrets manager (Vault, AWS Secrets Manager via the External Secrets Operator), is what actually protects a Secret's contents. Who can even read it in the first place is a separate question, answered by RBAC, not by anything on the Secret itself.
kubectl create secret generic db-credentials \
--from-literal=username=app \
--from-literal=password='correct horse battery staple'spec:
containers:
- name: app
image: docktionary/app:1.4.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: passwordSecret types
type doesn’t change how a Secret is stored — every type is still just base64 under the hood — but it does determine what keys Kubernetes expects and validates, and it's how other tooling (an Ingress controller looking for TLS, the kubelet looking for registry credentials) knows which Secret to use for what.
- Opaque — the default; arbitrary key-value data, used above.
- kubernetes.io/tls — expects
tls.crt/tls.key; what an Ingress’stls.secretNamepoints at. - kubernetes.io/dockerconfigjson — registry credentials, referenced from a Pod's
imagePullSecretsso the kubelet can pull a private image.
kubectl create secret docker-registry regcred \
--docker-server=ghcr.io \
--docker-username=you \
--docker-password=$GITHUB_TOKENImmutable ConfigMaps and Secrets
immutable: true locks the object's data after creation — any edit has to go through creating a new object instead. In exchange, the API server stops watching it for changes, which measurably reduces load on kube-apiserver in clusters with a lot of ConfigMaps. Pair it with a name that includes a content hash (what Kustomize's configMapGenerator does automatically) so a Deployment picks up a "change" by referencing a new object name, which also happens to trigger a proper rolling restart — something editing an existing ConfigMap in place never reliably does.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-a1b2c3
immutable: true
data:
LOG_LEVEL: "info"PersistentVolume & PersistentVolumeClaim
A PersistentVolumeClaim (PVC) is how a Pod asks for storage without knowing the details of the underlying disk. A PersistentVolume (PV) represents that actual storage, either provisioned ahead of time or created on demand by a StorageClass.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: standard
resources:
requests:
storage: 10Gispec:
containers:
- name: postgres
image: postgres:16-alpine
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: db-dataStorageClass
A StorageClass tells the cluster how to provision a volume automatically when a PVC asks for one — which provisioner to use (an EBS volume, a Persistent Disk, an Azure Disk) and with what parameters.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
volumeBindingMode: WaitForFirstConsumerWaitForFirstConsumer delays provisioning until a Pod actually needs the volume, so the disk gets created in the same availability zone the Pod was scheduled into.
Checking it all from the command line
kubectl get configmaps,secrets
kubectl get pvc,pv,storageclass
kubectl describe pvc db-data