Today's mission: stop hardcoding values into pod manifests and learn how to inject them properly using ConfigMaps and Secrets. I also managed to break a few things along the way, which honestly taught me more than if everything had just worked first try. Let's get into it.
What is a ConfigMap?
As your manifest grows, it becomes harder to manage a pile of environment variables directly inside it. A ConfigMap lets you:
- Pull those key-value pairs out of the manifest and store them as their own Kubernetes object
- Inject that ConfigMap into a pod as environment variables (or as files, but that's for another day)
- Reuse the same ConfigMap across multiple pods, so you're not repeating yourself everywhere
Basically, it's a clean way to separate configuration from your application definition.
Creating a ConfigMap
kubectl create cm app-cm --from-literal=firstname=adeoye --from-literal=lastname=malumi
Here, firstname and lastname are the keys, and adeoye / malumi are the values.
My first "gotcha" of the day
I actually tried to split this command across two lines like this:
kubectl create cm app-cm --from-literal=firstname=adeoye \
--from-literal=lastname=malumi
First attempt gave me:
error: exactly one NAME is required, got 2
Turns out I had a trailing space after the backslash on the first line. In bash, the \ has to be the very last character on the line for line continuation to work. A space after it breaks the continuation, and the shell just treats the next line as a separate (garbled) argument. When I finally got the syntax right, kubectl describe cm app-cm showed the value for firstname as adeoye--from-literal=lastname=malumi — my second flag had literally been glued onto the first value as text!
Lesson learned: either keep the command on one line, or make sure there's truly nothing (not even a space) after your \.
Once fixed, describe showed exactly what I wanted:
Data
====
firstname:
----
adeoye
lastname:
----
malumi
Injecting a ConfigMap into a Pod
Here's the pod manifest I used to consume the ConfigMap as environment variables:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app.kubernetes.io/name: MyApp
spec:
containers:
- name: myapp-container
image: busybox:1.28
env:
- name: FIRSTNAME
valueFrom:
configMapKeyRef:
name: app-cm
key: firstname
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
The valueFrom.configMapKeyRef block tells Kubernetes: "don't hardcode this value — go fetch it from the app-cm ConfigMap, using the firstname key."
Gotcha #2: Pods are mostly immutable
After creating my ConfigMap with both firstname and lastname, I went back and added a LASTNAME env var to my already-running pod's manifest, then ran:
kubectl apply -f pod.yaml
And got slapped with:
The Pod "myapp-pod" is invalid: spec: Forbidden: pod updates may not change fields other than
`spec.containers[*].image`,`spec.initContainers[*].image`,`spec.activeDeadlineSeconds`,
`spec.tolerations` (only additions to existing tolerations),
`spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)
This was a great reminder: a bare Pod's spec is mostly frozen once it's created. Kubernetes only lets you patch a small allowlist of fields in-place — image, a couple of grace period settings, and tolerations. Environment variables are not on that list.
Two ways around this:
Option 1 — Delete and recreate:
kubectl delete pod myapp-pod
kubectl apply -f pod.yaml
Option 2 — Force replace in one shot:
kubectl replace --force -f pod.yaml
I went with replace --force, and it worked perfectly:
❯ kubectl replace --force -f pod.yaml
pod "myapp-pod" deleted from default namespace
pod/myapp-pod replaced
Verified inside the container:
❯ kubectl exec -it myapp-pod -- sh
/ # echo $FIRSTNAME
adeoye
/ # echo $LASTNAME
malumi
Side note: this is actually one of the reasons Deployments exist and are preferred over bare Pods in real-world use. A Deployment lets you update the pod template (image, env vars, etc.) and kubectl apply will handle the rolling replacement of pods for you — no manual delete/recreate dance required.
Secrets: same idea, but for sensitive data
Secrets work almost identically to ConfigMaps, except they're meant for sensitive values like passwords, tokens, or usernames you don't want sitting in plain text in your manifests.
I followed the official Kubernetes docs on defining container environment variables using Secret data.
Creating a Secret
kubectl create secret generic backend-user --from-literal=backend-username='adeoye-admin'
Using the sample pod from the docs
kubectl create -f https://k8s.io/examples/pods/inject/pod-single-secret-env-variable.yaml
This pod injects the secret as an environment variable called SECRET_USERNAME.
One more small hiccup
Right after creating the pod, I tried to exec into it immediately:
❯ kubectl exec -i -t env-single-secret -- /bin/sh -c 'echo $SECRET_USERNAME'
error: Internal error occurred: unable to upgrade connection: container not found ("envars-test-container")
Checking kubectl get po showed the pod was still ContainerCreating. Classic case of being too impatient — I just needed to wait a few seconds for the container to actually spin up. Once it hit Running:
❯ kubectl exec -i -t env-single-secret -- /bin/sh -c 'echo $SECRET_USERNAME'
adeoye-admin
And there it is — the secret successfully injected as an env var.
Key Takeaways
- ConfigMaps decouple non-sensitive configuration from your pod manifests, and can be reused across multiple pods.
- Secrets work the same way but are the right home for sensitive values.
- Both can be injected into a pod's containers via
env.valueFrom.configMapKeyRef or secretKeyRef.
- Watch your shell syntax — a stray space after a line-continuation
\ will silently mangle your command.
- Pods are mostly immutable once created. Only image, tolerations, and a couple of grace-period fields can be patched live. Everything else means delete + recreate, or
kubectl replace --force. This is a big part of why Deployments exist.
- Give newly created pods a moment to leave
ContainerCreating before you exec into them.
That's Day 19 done — small errors, but each one taught me something I'll remember longer than if it had just worked on the first try.