Module 9 — ConfigMaps & Secrets
Overview
ConfigMaps and Secrets decouple configuration from container images. ConfigMaps hold non-sensitive data, Secrets hold sensitive data (base64-encoded, not encrypted by default). The CKA exam tests creation, consumption (env vars vs volumes), and immutability. This module covers all three topics in depth.
1. ConfigMaps
1.1 What Is a ConfigMap?
A ConfigMap stores key-value pairs of non-sensitive configuration data. Pods consume them as environment variables or mounted files.
| ┌──────────────────┐ ┌──────────────┐
│ ConfigMap │ │ Pod │
│ │ │ │
│ DB_HOST=mysql │──env──▶ │ $DB_HOST │
│ DB_PORT=3306 │──env──▶ │ $DB_PORT │
│ app.conf=... │──vol──▶ │ /etc/config/│
└──────────────────┘ └──────────────┘
|
1.2 Creating ConfigMaps
Imperative — From Literals
| kubectl create configmap app-config \
--from-literal=DB_HOST=mysql \
--from-literal=DB_PORT=3306 \
--from-literal=LOG_LEVEL=info
|
Imperative — From a File
| # Create a config file
cat <<EOF > app.properties
database.host=mysql
database.port=3306
database.name=myapp
EOF
# Create ConfigMap from file (key = filename, value = file contents)
kubectl create configmap app-config --from-file=app.properties
# Custom key name
kubectl create configmap app-config --from-file=config.txt=app.properties
# From an entire directory (each file becomes a key)
kubectl create configmap app-config --from-file=./config-dir/
|
Imperative — From env File
| # env file format (KEY=VALUE per line)
cat <<EOF > app.env
DB_HOST=mysql
DB_PORT=3306
EOF
kubectl create configmap app-config --from-env-file=app.env
|
Declarative YAML
| apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: default
data:
DB_HOST: mysql
DB_PORT: "3306" # values are always strings
LOG_LEVEL: info
app.conf: | # multi-line value (file content)
server.port=8080
server.host=0.0.0.0
logging.level=INFO
|
Generate YAML
| kubectl create configmap app-config \
--from-literal=DB_HOST=mysql \
--from-literal=DB_PORT=3306 \
--dry-run=client -o yaml > configmap.yaml
|
1.3 Inspecting ConfigMaps
| # List
kubectl get configmaps
kubectl get cm # shortname
# View contents
kubectl describe cm app-config
kubectl get cm app-config -o yaml
|
2. Secrets
2.1 What Is a Secret?
A Secret stores sensitive data — passwords, tokens, certificates, SSH keys. Values are base64-encoded (NOT encrypted) by default.
2.2 Secret Types
| Type |
Usage |
Opaque (default) |
Arbitrary key-value data |
kubernetes.io/tls |
TLS certificate + private key |
kubernetes.io/dockerconfigjson |
Docker registry credentials |
kubernetes.io/service-account-token |
ServiceAccount token (legacy) |
kubernetes.io/basic-auth |
Username + password |
kubernetes.io/ssh-auth |
SSH private key |
2.3 Creating Secrets
Imperative — Generic (Opaque)
| kubectl create secret generic db-secret \
--from-literal=DB_USER=admin \
--from-literal=DB_PASSWORD=s3cret123
|
Imperative — From Files
| # Create secret from certificate files
kubectl create secret generic tls-data \
--from-file=cert.pem=/path/to/cert.pem \
--from-file=key.pem=/path/to/key.pem
# TLS type (validates cert + key)
kubectl create secret tls my-tls \
--cert=/path/to/tls.crt \
--key=/path/to/tls.key
|
Imperative — Docker Registry
| kubectl create secret docker-registry regcred \
--docker-server=https://index.docker.io/v1/ \
--docker-username=<user> \
--docker-password=<password> \
--docker-email=<email>
|
Declarative YAML
Values must be base64-encoded:
| # Encode values
echo -n 'admin' | base64 # YWRtaW4=
echo -n 's3cret123' | base64 # czNjcmV0MTIz
|
| apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
DB_USER: YWRtaW4= # base64 of "admin"
DB_PASSWORD: czNjcmV0MTIz # base64 of "s3cret123"
|
Or use stringData to avoid manual encoding:
| apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData: # plain text — Kubernetes encodes it
DB_USER: admin
DB_PASSWORD: s3cret123
|
CKA Tip: Use stringData in YAML to avoid base64 encoding mistakes. Use --from-literal imperatively for speed.
2.4 Inspecting Secrets
| # List
kubectl get secrets
# View (values are hidden)
kubectl describe secret db-secret
# View base64-encoded values
kubectl get secret db-secret -o yaml
# Decode a specific value
kubectl get secret db-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
|
2.5 Security Considerations
| Aspect |
Reality |
| base64 encoding |
NOT encryption — anyone with API access can decode |
| etcd storage |
Secrets stored in plain text in etcd by default |
| Encryption at rest |
Must be explicitly configured (EncryptionConfiguration) |
| RBAC |
Restrict get/list on Secrets to authorized users only |
| Namespace isolation |
Secrets are namespaced — pods can only access Secrets in their namespace |
CKA Tip: The exam won't ask you to configure encryption at rest, but know that base64 ≠ encryption.
3. Consuming ConfigMaps and Secrets — Environment Variables
3.1 Single Key as Environment Variable
| apiVersion: v1
kind: Pod
metadata:
name: env-pod
spec:
containers:
- name: app
image: nginx
env:
- name: DATABASE_HOST # env var name in the container
valueFrom:
configMapKeyRef:
name: app-config # ConfigMap name
key: DB_HOST # key in the ConfigMap
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret # Secret name
key: DB_PASSWORD # key in the Secret
|
3.2 All Keys as Environment Variables
| apiVersion: v1
kind: Pod
metadata:
name: envfrom-pod
spec:
containers:
- name: app
image: nginx
envFrom:
- configMapRef:
name: app-config # all keys become env vars
- secretRef:
name: db-secret # all keys become env vars
|
With optional prefix:
| envFrom:
- configMapRef:
name: app-config
prefix: APP_ # DB_HOST → APP_DB_HOST
|
3.3 Verify Environment Variables
| kubectl exec env-pod -- env | grep DATABASE
# DATABASE_HOST=mysql
# DATABASE_PASSWORD=s3cret123
kubectl exec envfrom-pod -- env | sort
|
4. Consuming ConfigMaps and Secrets — Volume Mounts
4.1 Mount Entire ConfigMap as a Directory
Each key becomes a file in the mounted directory:
| apiVersion: v1
kind: Pod
metadata:
name: vol-pod
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: config-volume
mountPath: /etc/config # directory where files appear
volumes:
- name: config-volume
configMap:
name: app-config
|
Result inside the container:
| kubectl exec vol-pod -- ls /etc/config
# DB_HOST
# DB_PORT
# LOG_LEVEL
# app.conf
kubectl exec vol-pod -- cat /etc/config/DB_HOST
# mysql
kubectl exec vol-pod -- cat /etc/config/app.conf
# server.port=8080
# server.host=0.0.0.0
# logging.level=INFO
|
4.2 Mount Entire Secret as a Directory
| spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true # best practice for secrets
volumes:
- name: secret-volume
secret:
secretName: db-secret
|
Values are automatically decoded from base64 when mounted as files:
| kubectl exec vol-pod -- cat /etc/secrets/DB_PASSWORD
# s3cret123 (plain text, not base64)
|
4.3 Mount Specific Keys Only
| volumes:
- name: config-volume
configMap:
name: app-config
items:
- key: app.conf # only this key
path: application.conf # mounted as this filename
|
| kubectl exec vol-pod -- ls /etc/config
# application.conf (only this file)
|
4.4 Mount a Single Key as a File (Without Overwriting the Directory)
Using subPath prevents the volume mount from replacing the entire directory:
| spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/nginx.conf # single file path
subPath: nginx.conf # key from ConfigMap
volumes:
- name: config-volume
configMap:
name: nginx-config
|
Important: subPath mounts do NOT receive automatic updates when the ConfigMap changes.
4.5 File Permissions
| volumes:
- name: secret-volume
secret:
secretName: db-secret
defaultMode: 0400 # read-only for owner
|
5. Environment Variables vs Volume Mounts
| Aspect |
Environment Variables |
Volume Mounts |
| Format |
Key-value pairs as env vars |
Keys as files in a directory |
| Auto-update |
❌ No — requires pod restart |
✅ Yes — kubelet syncs periodically (~60s) |
subPath update |
N/A |
❌ No — subPath mounts don't auto-update |
| Binary data |
❌ Not suitable |
✅ Supports binary (certs, keystores) |
| Visibility |
Visible in kubectl describe pod |
Not visible in describe |
| Security |
Env vars can leak in logs/crash dumps |
Files are slightly more secure |
| Use case |
Simple key-value config |
Config files, certificates, multi-line data |
5.1 When to Use Which
| Scenario |
Recommendation |
| Database connection string |
Environment variable |
| API key or password |
Volume mount (more secure) or env var |
| Full config file (nginx.conf, app.properties) |
Volume mount |
| TLS certificates |
Volume mount |
| Feature flags (simple on/off) |
Environment variable |
| Config that must auto-update without restart |
Volume mount (not subPath) |
5.2 Auto-Update Behavior
When a ConfigMap or Secret is updated:
| # Update a ConfigMap
kubectl edit cm app-config
# OR
kubectl create cm app-config --from-literal=DB_HOST=new-mysql --dry-run=client -o yaml | kubectl apply -f -
|
| Mount type |
Auto-updates? |
Delay |
| Volume mount (full) |
✅ Yes |
~60 seconds (kubelet sync period) |
Volume mount with subPath |
❌ No |
Never — requires pod restart |
| Environment variable |
❌ No |
Never — requires pod restart |
6. Immutable ConfigMaps and Secrets
6.1 Why Immutable?
| Benefit |
Detail |
| Performance |
Kubelet stops watching for changes — reduces API server load |
| Safety |
Prevents accidental changes to critical configuration |
| Scale |
Significant in large clusters with thousands of ConfigMaps/Secrets |
6.2 Creating Immutable ConfigMaps
| apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v1
data:
DB_HOST: mysql
DB_PORT: "3306"
immutable: true # ← cannot be changed after creation
|
6.3 Creating Immutable Secrets
| apiVersion: v1
kind: Secret
metadata:
name: db-secret-v1
type: Opaque
immutable: true # ← cannot be changed after creation
stringData:
DB_USER: admin
DB_PASSWORD: s3cret123
|
6.4 What Happens When You Try to Modify
| kubectl edit cm app-config-v1
# error: configmaps "app-config-v1" is immutable
kubectl patch cm app-config-v1 -p '{"data":{"DB_HOST":"new-mysql"}}'
# error: the object "app-config-v1" is invalid: data: Forbidden: field is immutable when `immutable` is set
|
6.5 Updating Immutable ConfigMaps/Secrets
You can't modify them — you must:
- Create a new ConfigMap/Secret with a new name (e.g.,
app-config-v2)
- Update the pod/deployment to reference the new name
- Delete the old ConfigMap/Secret
| # 1. Create new version
kubectl create configmap app-config-v2 \
--from-literal=DB_HOST=new-mysql \
--from-literal=DB_PORT=3306
# Make it immutable
kubectl patch cm app-config-v2 -p '{"immutable": true}'
# 2. Update the deployment to use v2
kubectl set env deployment/myapp --from=configmap/app-config-v2
# 3. Delete old version
kubectl delete cm app-config-v1
|
6.6 Making an Existing ConfigMap Immutable
| # You CAN set immutable from false/unset to true
kubectl patch cm app-config -p '{"immutable": true}'
# But you CANNOT set it back to false
kubectl patch cm app-config -p '{"immutable": false}'
# error: field is immutable
|
CKA Tip: immutable: true is a one-way operation. Once set, you cannot unset it or modify the data. You must delete and recreate.
7. Projected Volumes — Combining Multiple Sources
You can mount ConfigMaps, Secrets, and other sources into a single directory:
| spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: all-config
mountPath: /etc/config
volumes:
- name: all-config
projected:
sources:
- configMap:
name: app-config
items:
- key: app.conf
path: app.conf
- secret:
name: db-secret
items:
- key: DB_PASSWORD
path: db-password
- downwardAPI:
items:
- path: pod-name
fieldRef:
fieldPath: metadata.name
|
Result:
| kubectl exec pod -- ls /etc/config
# app.conf
# db-password
# pod-name
|
8. Practice Exercises
Exercise 1 — Create and Consume a ConfigMap
| # 1. Create a ConfigMap
kubectl create configmap web-config \
--from-literal=APP_COLOR=blue \
--from-literal=APP_MODE=production
# 2. Create a pod using env vars
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: web-pod
spec:
containers:
- name: web
image: nginx
envFrom:
- configMapRef:
name: web-config
EOF
# 3. Verify
kubectl exec web-pod -- env | grep APP
# APP_COLOR=blue
# APP_MODE=production
# 4. Clean up
kubectl delete pod web-pod
kubectl delete cm web-config
|
Exercise 2 — Mount a ConfigMap as a File
| # 1. Create a ConfigMap with a config file
kubectl create configmap nginx-config --from-literal=nginx.conf="server { listen 8080; }"
# 2. Mount it as a volume
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx-custom
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d
volumes:
- name: config
configMap:
name: nginx-config
EOF
# 3. Verify the file exists
kubectl exec nginx-custom -- cat /etc/nginx/conf.d/nginx.conf
# 4. Clean up
kubectl delete pod nginx-custom
kubectl delete cm nginx-config
|
Exercise 3 — Secrets as Environment Variables and Volumes
| # 1. Create a Secret
kubectl create secret generic app-secret \
--from-literal=API_KEY=abc123 \
--from-literal=DB_PASS=supersecret
# 2. Create a pod using both env and volume
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: secret-pod
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "echo env=\$API_KEY; cat /etc/secrets/DB_PASS; sleep 3600"]
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: app-secret
key: API_KEY
volumeMounts:
- name: secret-vol
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-vol
secret:
secretName: app-secret
EOF
# 3. Check logs
kubectl logs secret-pod
# env=abc123
# supersecret
# 4. Verify volume mount (decoded from base64)
kubectl exec secret-pod -- cat /etc/secrets/DB_PASS
# supersecret
# 5. Clean up
kubectl delete pod secret-pod
kubectl delete secret app-secret
|
Exercise 4 — Auto-Update Test
| # 1. Create a ConfigMap and mount it
kubectl create configmap update-test --from-literal=message=hello
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: update-pod
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "while true; do cat /etc/config/message; sleep 5; done"]
volumeMounts:
- name: config
mountPath: /etc/config
volumes:
- name: config
configMap:
name: update-test
EOF
# 2. Check current value
kubectl exec update-pod -- cat /etc/config/message
# hello
# 3. Update the ConfigMap
kubectl patch cm update-test -p '{"data":{"message":"world"}}'
# 4. Wait ~60 seconds and check again
sleep 70
kubectl exec update-pod -- cat /etc/config/message
# world ← auto-updated!
# 5. Clean up
kubectl delete pod update-pod
kubectl delete cm update-test
|
Exercise 5 — Immutable ConfigMap
| # 1. Create an immutable ConfigMap
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: locked-config
data:
KEY: value1
immutable: true
EOF
# 2. Try to modify it
kubectl patch cm locked-config -p '{"data":{"KEY":"value2"}}'
# Error — immutable
# 3. Verify you must delete and recreate
kubectl delete cm locked-config
kubectl create configmap locked-config --from-literal=KEY=value2
# 4. Clean up
kubectl delete cm locked-config
|
Exercise 6 — Decode a Secret
| # 1. Create a Secret
kubectl create secret generic exam-secret \
--from-literal=username=admin \
--from-literal=password=exam-pass-123
# 2. View the base64-encoded values
kubectl get secret exam-secret -o yaml
# 3. Decode each value
kubectl get secret exam-secret -o jsonpath='{.data.username}' | base64 -d
# admin
kubectl get secret exam-secret -o jsonpath='{.data.password}' | base64 -d
# exam-pass-123
# 4. Clean up
kubectl delete secret exam-secret
|
9. Key Takeaways for the CKA Exam
| Point |
Detail |
| Imperative creation is fastest |
kubectl create configmap --from-literal / kubectl create secret generic --from-literal |
stringData avoids base64 mistakes |
Use it in Secret YAML instead of manually encoding data |
envFrom for all keys at once |
configMapRef / secretRef — injects every key as an env var |
| Volume mounts auto-update |
~60s delay, except subPath mounts which never update |
| Env vars never auto-update |
Requires pod restart to pick up changes |
| Secrets are base64, not encrypted |
Know the difference — RBAC is the primary access control |
readOnly: true for secret volumes |
Best practice — prevents container from modifying mounted secrets |
| Immutable is one-way |
Once immutable: true is set, you cannot modify or unset it |
| Decode secrets with base64 -d |
kubectl get secret <name> -o jsonpath='{.data.<key>}' \| base64 -d |
items for selective mounting |
Mount only specific keys with custom filenames |
subPath for single file |
Doesn't overwrite the directory, but loses auto-update |
Previous: 08-scheduling.md — Scheduling
Next: 10-services.md — Services