ConfigMaps and Secrets
Applications need configuration. Kubernetes gives you ConfigMaps for non-sensitive configuration and Secrets for sensitive values. Both help keep configuration out of container images and make workloads easier to change across environments.
What this lesson covers
In the earlier lessons, you created Pods, Deployments, and Services. Now we need to configure applications without rebuilding container images every time a setting changes.
By the end of this lesson, you should understand:
- What a ConfigMap is
- What a Secret is
- When to use a ConfigMap instead of a Secret
- How to create ConfigMaps from commands and YAML
- How to use ConfigMaps as environment variables
- How to mount ConfigMaps as files
- How to create Secrets
- How to use Secrets as environment variables
- Why Kubernetes Secrets still need careful handling
- How to clean up the lab resources
The short version
A ConfigMap stores non-sensitive configuration data.
A Secret stores sensitive configuration data, such as passwords, tokens, and keys.
Both can be consumed by Pods as:
- Environment variables
- Mounted files
- Command or argument values
Container image: Application and dependencies ConfigMap: Non-sensitive settings Secret: Sensitive settings Pod: Runs the container and injects configuration at runtime
Why not put configuration in the image?
If configuration is baked into the image, every environment change requires a new image.
That creates problems:
- Development, test, and production may require different settings.
- Changing one value requires a rebuild.
- Secrets may accidentally end up inside images.
- Images become environment-specific instead of reusable.
A better pattern is:
Build the image once. Supply environment-specific configuration at deployment time.
ConfigMap vs Secret
Use a ConfigMap for non-sensitive values:
- Application mode
- Feature flags
- Log level
- Non-sensitive URLs
- Non-sensitive application settings
Use a Secret for sensitive values:
- Passwords
- API keys
- Tokens
- Private keys
- Database credentials
Important note about Kubernetes Secrets
Kubernetes Secrets are not magic vaults.
By default, Secret values are base64-encoded. Base64 is encoding, not encryption. Anyone who can read the Secret can decode the value.
In real environments, Secret protection depends on additional controls such as:
- RBAC permissions
- Encryption at rest for Kubernetes Secrets
- Restricting who can exec into Pods
- Avoiding unnecessary environment variable exposure
- Using external secret managers where appropriate
- Rotating credentials
Before you start the lab
This lab assumes:
- Docker Desktop is running
- Your kind cluster exists
- kubectl is installed
- Your current context is kind-stealth-k8s-lab
Check your context:
kubectl config current-context
You should see:
kind-stealth-k8s-lab
Check your node:
kubectl get nodes
If your cluster no longer exists, recreate it:
kind create cluster --name stealth-k8s-lab
Step 1: Create a ConfigMap from literal values
Create a ConfigMap with a few simple application settings:
kubectl create configmap app-config-lab --from-literal=APP_MODE=training --from-literal=LOG_LEVEL=debug --from-literal=FEATURE_FLAG=true
Check the ConfigMap:
kubectl get configmaps
View the ConfigMap:
kubectl describe configmap app-config-lab
You should see the keys and values stored in the ConfigMap.
Step 2: View the ConfigMap as YAML
Run:
kubectl get configmap app-config-lab -o yaml
Look for:
- apiVersion
- kind
- metadata
- data
The data section contains the configuration key-value pairs.
Step 3: Create a Pod that uses ConfigMap values as environment variables
Create a file named:
configmap-env-pod.yaml
Paste this into the file:
apiVersion: v1
kind: Pod
metadata:
name: configmap-env-pod
spec:
containers:
- name: app
image: busybox:latest
command: ["sh", "-c", "echo APP_MODE=$APP_MODE; echo LOG_LEVEL=$LOG_LEVEL; echo FEATURE_FLAG=$FEATURE_FLAG; sleep 3600"]
env:
- name: APP_MODE
valueFrom:
configMapKeyRef:
name: app-config-lab
key: APP_MODE
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config-lab
key: LOG_LEVEL
- name: FEATURE_FLAG
valueFrom:
configMapKeyRef:
name: app-config-lab
key: FEATURE_FLAG
Apply it:
kubectl apply -f configmap-env-pod.yaml
Check the Pod:
kubectl get pods
Step 4: Check the Pod logs
Run:
kubectl logs configmap-env-pod
You should see output similar to:
APP_MODE=training LOG_LEVEL=debug FEATURE_FLAG=true
This proves that the Pod received values from the ConfigMap as environment variables.
Step 5: Create a ConfigMap from YAML
Create a file named:
app-file-config.yaml
Paste this into the file:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-file-config-lab
data:
app.properties: |
app.name=stealth-k8s-lab
app.mode=training
log.level=debug
feature.enabled=true
Apply it:
kubectl apply -f app-file-config.yaml
Check it:
kubectl describe configmap app-file-config-lab
This ConfigMap stores a file-like configuration value named app.properties.
Step 6: Mount a ConfigMap as a file
Create a file named:
configmap-volume-pod.yaml
Paste this into the file:
apiVersion: v1
kind: Pod
metadata:
name: configmap-volume-pod
spec:
containers:
- name: app
image: busybox:latest
command: ["sh", "-c", "cat /etc/app-config/app.properties; sleep 3600"]
volumeMounts:
- name: config-volume
mountPath: /etc/app-config
volumes:
- name: config-volume
configMap:
name: app-file-config-lab
Apply it:
kubectl apply -f configmap-volume-pod.yaml
Check the logs:
kubectl logs configmap-volume-pod
You should see the contents of the mounted app.properties file.
Step 7: Exec into the Pod and inspect the mounted file
Run:
kubectl exec -it configmap-volume-pod -- /bin/sh
Inside the container, run:
ls /etc/app-config cat /etc/app-config/app.properties exit
This shows that Kubernetes mounted the ConfigMap into the container filesystem as a file.
Step 8: Create a Secret from literal values
Now create a Secret for a fake username and password:
kubectl create secret generic app-secret-lab --from-literal=DB_USERNAME=training_user --from-literal=DB_PASSWORD=not-a-real-password
Check the Secret:
kubectl get secrets
Describe the Secret:
kubectl describe secret app-secret-lab
Notice that describe does not print the Secret values directly.
Step 9: View the Secret as YAML
Run:
kubectl get secret app-secret-lab -o yaml
You will see values under the data section, but they will be base64-encoded.
This is important: base64 is not encryption. It is easy to decode if someone has permission to read the Secret.
Step 10: Decode a Secret value for learning
To understand what base64 encoding means, get the encoded password:
kubectl get secret app-secret-lab -o jsonpath="{.data.DB_PASSWORD}"
In PowerShell, you can decode it like this:
$encoded = kubectl get secret app-secret-lab -o jsonpath="{.data.DB_PASSWORD}"
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded))
You should see:
not-a-real-password
This is why Secret access must be controlled carefully.
Step 11: Use a Secret as environment variables
Create a file named:
secret-env-pod.yaml
Paste this into the file:
apiVersion: v1
kind: Pod
metadata:
name: secret-env-pod
spec:
containers:
- name: app
image: busybox:latest
command: ["sh", "-c", "echo DB_USERNAME=$DB_USERNAME; echo DB_PASSWORD is set; sleep 3600"]
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: app-secret-lab
key: DB_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret-lab
key: DB_PASSWORD
Apply it:
kubectl apply -f secret-env-pod.yaml
Check the logs:
kubectl logs secret-env-pod
You should see the username and a message confirming the password is set. The command intentionally does not print the password.
Step 12: Create a Secret from YAML
You can also define a Secret in YAML. Kubernetes supports stringData, which lets you write plain text values in the YAML file and have Kubernetes store them as Secret data.
Create a file named:
app-yaml-secret.yaml
Paste this into the file:
apiVersion: v1 kind: Secret metadata: name: app-yaml-secret-lab type: Opaque stringData: API_TOKEN: fake-training-token API_URL: https://api.example.local
Apply it:
kubectl apply -f app-yaml-secret.yaml
Check it:
kubectl describe secret app-yaml-secret-lab
This is useful for learning, but be careful in real projects. Plain text secret values in YAML files can easily end up committed to Git.
Step 13: Mount a Secret as a file
Create a file named:
secret-volume-pod.yaml
Paste this into the file:
apiVersion: v1
kind: Pod
metadata:
name: secret-volume-pod
spec:
containers:
- name: app
image: busybox:latest
command: ["sh", "-c", "ls /etc/app-secret; echo Secret files mounted; sleep 3600"]
volumeMounts:
- name: secret-volume
mountPath: /etc/app-secret
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: app-yaml-secret-lab
Apply it:
kubectl apply -f secret-volume-pod.yaml
Check the logs:
kubectl logs secret-volume-pod
Exec into the Pod and inspect the mounted files:
kubectl exec -it secret-volume-pod -- /bin/sh
Inside the container, run:
ls /etc/app-secret cat /etc/app-secret/API_URL exit
This shows that Secret keys can be mounted into a container as files.
Step 14: Update a ConfigMap value
Edit the ConfigMap directly:
kubectl edit configmap app-config-lab
Change:
LOG_LEVEL: debug
to:
LOG_LEVEL: info
Save and exit the editor.
Now check the original environment-variable Pod logs again:
kubectl logs configmap-env-pod
You will usually still see the original value printed when the container started.
Why the Pod did not automatically change
Environment variables are injected into the container when it starts. Updating the ConfigMap does not automatically rewrite the process environment inside an already-running container.
In real Deployments, changing configuration often requires restarting or rolling out the workload so the application receives the new values.
Step 15: Create a missing ConfigMap problem on purpose
Create a file named:
missing-configmap-pod.yaml
Paste this into the file:
apiVersion: v1
kind: Pod
metadata:
name: missing-configmap-pod
spec:
containers:
- name: app
image: busybox:latest
command: ["sh", "-c", "echo This should not start cleanly; sleep 3600"]
env:
- name: MISSING_VALUE
valueFrom:
configMapKeyRef:
name: configmap-that-does-not-exist
key: SOME_KEY
Apply it:
kubectl apply -f missing-configmap-pod.yaml
Check the Pod:
kubectl get pods kubectl describe pod missing-configmap-pod
Look at the events near the bottom. Kubernetes should report that the referenced ConfigMap was not found.
Step 16: Clean up
Delete the lab Pods:
kubectl delete -f configmap-env-pod.yaml kubectl delete -f configmap-volume-pod.yaml kubectl delete -f secret-env-pod.yaml kubectl delete -f secret-volume-pod.yaml kubectl delete -f missing-configmap-pod.yaml
Delete the ConfigMaps:
kubectl delete configmap app-config-lab kubectl delete -f app-file-config.yaml
Delete the Secrets:
kubectl delete secret app-secret-lab kubectl delete -f app-yaml-secret.yaml
Confirm cleanup:
kubectl get pods kubectl get configmaps kubectl get secrets
You should not see the lab Pods, ConfigMaps, or Secrets anymore.
Common ConfigMap and Secret troubleshooting commands
Use this flow when configuration is not working:
kubectl get configmaps kubectl describe configmap <configmap-name> kubectl get configmap <configmap-name> -o yaml kubectl get secrets kubectl describe secret <secret-name> kubectl get pod <pod-name> -o yaml kubectl describe pod <pod-name> kubectl logs <pod-name> kubectl get events --sort-by=.metadata.creationTimestamp
This usually tells you:
- Whether the ConfigMap or Secret exists
- Whether the key name matches what the Pod expects
- Whether the Pod references the correct object name
- Whether Kubernetes reported a missing ConfigMap or Secret
- Whether the application printed a configuration error
Security notes for Secrets
For real environments, keep these rules in mind:
- Do not commit real secrets to Git.
- Do not print secrets to logs.
- Restrict who can read Secrets with RBAC.
- Limit who can exec into Pods that contain sensitive values.
- Enable encryption at rest for Secrets where appropriate.
- Use an external secret manager when the environment requires stronger controls.
- Rotate sensitive values when people leave, systems change, or exposure is suspected.
Check your understanding
Use these questions to test whether the ConfigMap and Secret concepts made sense.
- What type of data should go in a ConfigMap?
- What type of data should go in a Secret?
- Why should configuration not be baked directly into a container image?
- What are two ways a Pod can consume ConfigMap values?
- What section of a ConfigMap stores key-value data?
- Are Kubernetes Secret values encrypted just because they appear base64-encoded?
- Why should you avoid printing secret values to logs?
- What happens to environment variables in a running container when a ConfigMap is updated?
- What command can show whether a Pod failed because a ConfigMap was missing?
- Why is stringData useful when creating Secrets from YAML?
- Why is putting real secret values in YAML risky?
- What are three security controls that help protect Kubernetes Secrets?
Answer key
- Non-sensitive configuration such as log level, feature flags, and application mode.
- Sensitive values such as passwords, tokens, API keys, and private keys.
- Because configuration changes would require rebuilding the image and could make images environment-specific or leak sensitive values.
- As environment variables or mounted files.
- The data section.
- No. Base64 is encoding, not encryption.
- Logs are often stored, shipped, indexed, and accessible to more people or systems.
- They usually do not change inside the already-running process. The Pod or workload normally needs to restart to receive new environment variable values.
- kubectl describe pod <pod-name>, especially the events section.
- It lets you write plain text values in the YAML and have Kubernetes store them as Secret data.
- Because the file may be committed to Git, copied, shared, or stored insecurely.
- Examples: RBAC restrictions, encryption at rest, limiting exec access, external secret managers, avoiding secret logs, and credential rotation.