Configuration with ConfigMap and Secret
Until now your containers carried everything inside the image. But the same image has to work in development, in staging and in production, and what changes between environments (URLs, UI colors, passwords) cannot live baked into it. Kubernetes separates that material into two objects: the ConfigMap for ordinary configuration and the Secret for sensitive data.
This is the book's Configuration chapter, with one difference: here you are going to check for yourself what a Secret protects and what it does not.
In this lesson you will create one of each and inject them into a Pod in the two possible ways: as environment variables and as a mounted file. Work from the dev-machine tab.
Step 1: The ConfigMap
Create the file configmap.yaml. Notice that it mixes two styles of key: a simple value and a whole file.
cat << 'EOF' > configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
data:
API_URL: "http://api:8080"
app.properties: |
server.port=8080
server.timeout=30s
EOF
The YAML, explained in questions and answers
Where is the spec?
There is none. ConfigMaps do not describe a desired state to reconcile, they are pure data storage, so they use a flat data field of key-value pairs.
Why is one key called app.properties?
Because the keys of a ConfigMap can be file names, and their value the whole content (the YAML | keeps the line breaks). When you mount the ConfigMap as a volume, each key becomes a file with that name.
Is there a size limit?
Yes, 1 MiB per ConfigMap. It is configuration, not data storage; the next module is for that.
Apply it:
kubectl apply -f configmap.yaml
kubectl describe configmap web-config
Step 2: The Secret
Create secret.yaml:
cat << 'EOF' > secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: tienda
password: s3cr3t0
EOF
The YAML, explained in questions and answers
What does type: Opaque mean?
It is the generic type for arbitrary user data. There are specialized types (for image registry credentials, TLS certificates...) that the API validates in a specific way.
Why stringData and not data?
In a Secret, the data field demands base64-encoded values. stringData is the writing shortcut: you pass plain text and the API encodes it for you when it stores it. Check it after applying: kubectl get secret db-credentials -o yaml gives you back the value already in base64, under data.
So, does base64 protect the password?
No, and that is the most important question of the lesson. Base64 is encoding, not encryption: anyone with read access to the Secret recovers the value with base64 -d. The real protection of Secrets comes from other layers: RBAC access control (the Security module) and encryption at rest in etcd. A Secret is, above all, a label that says "this is sensitive, treat it differently".
Apply it:
kubectl apply -f secret.yaml
kubectl get secret db-credentials -o yaml
Step 3: The Pod that consumes it all
Now the main course: a Pod that receives the ConfigMap and the Secret through both routes. Create pod-web.yaml:
cat << 'EOF' > pod-web.yaml
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: app
image: ghcr.io/iximiuz/labs/nginx:alpine
env:
- name: API_URL
valueFrom:
configMapKeyRef:
name: web-config
key: API_URL
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: config-vol
mountPath: /etc/config
readOnly: true
volumes:
- name: config-vol
configMap:
name: web-config
items:
- key: app.properties
path: app.properties
EOF
The YAML, explained in questions and answers
What does valueFrom do compared with a plain value?
A value fixes the data in the Pod manifest; valueFrom references it from another object (configMapKeyRef or secretKeyRef). The Pod manifest stays the same in every environment and only the ConfigMap or Secret of each one changes.
Is there a way to bring in all the keys at once?
Yes, envFrom with configMapRef or secretRef injects every key as a variable. It is convenient, but valueFrom key by key documents better what the application really consumes.
How do volumes and volumeMounts relate?
It is a pattern in two halves that will come back in the next lesson: volumes (at Pod level) declares which volume exists and where its content comes from, and volumeMounts (at container level) declares where it shows up inside that container. Here the volume comes from a ConfigMap, and each key listed in items materializes as a file under /etc/config.
What if the referenced ConfigMap did not exist?
The Pod stays in CreateContainerConfigError until it does. Configuration references are hard dependencies of the startup.
If I change the ConfigMap, does the Pod change live?
It depends on the route. Files mounted as a volume update on their own after a while; environment variables do not, they are fixed when the container is created and only change by recreating the Pod. It is one of the reasons why many applications prefer to read configuration files.
Apply it and check from the inside, which is where it counts:
kubectl apply -f pod-web.yaml
kubectl wait --for=condition=Ready pod/web --timeout=60s
kubectl exec web -- sh -c 'echo $API_URL'
kubectl exec web -- sh -c 'echo $DB_PASSWORD'
kubectl exec web -- cat /etc/config/app.properties
Summary
- The ConfigMap holds configuration; the Secret, sensitive data with special treatment (which is not encryption on its own).
- Two ways to consume them: environment variables (
valueFrom/envFrom) and mounted files (volumes+volumeMounts). - ConfigMap volumes refresh live; environment variables do not.
CreateContainerConfigErroralmost always means a configuration reference that does not exist.
- Previous lesson
- CoreDNS and the internal DNS
- Next lesson
- Challenge: pull an image from a private registry