Kubernetes Core Concepts

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
Field note: Configuration should not be hardcoded into container images. The same image should be usable across environments with different configuration supplied at runtime.

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
Field note: Do not put passwords, tokens, or API keys in ConfigMaps. A ConfigMap is not meant for sensitive data.

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
Field note: A Kubernetes Secret is better than putting a password in a ConfigMap or image, but it still needs access control and operational discipline.

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.

Field note: Environment variables are common for simple configuration, but remember that they are visible to the process environment and may be exposed through debugging tools or application error output.

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.

Field note: Kubernetes hides Secret values from some normal output, but anyone with permission to get the Secret can access the encoded values and decode them.

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.

Field note: Avoid printing secrets to logs. Logs are often collected, shipped, stored, and viewed by more people than the application runtime itself.

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.

Field note: How configuration updates are picked up depends on how the application consumes configuration. Environment variables are read at process start. Mounted files can update, but the application still needs to reload or reread them.

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.
Bottom line: ConfigMaps are for non-sensitive configuration. Secrets are for sensitive values, but they still require careful access control. Both help separate application configuration from the container image.

Check your understanding

Use these questions to test whether the ConfigMap and Secret concepts made sense.

  1. What type of data should go in a ConfigMap?
  2. What type of data should go in a Secret?
  3. Why should configuration not be baked directly into a container image?
  4. What are two ways a Pod can consume ConfigMap values?
  5. What section of a ConfigMap stores key-value data?
  6. Are Kubernetes Secret values encrypted just because they appear base64-encoded?
  7. Why should you avoid printing secret values to logs?
  8. What happens to environment variables in a running container when a ConfigMap is updated?
  9. What command can show whether a Pod failed because a ConfigMap was missing?
  10. Why is stringData useful when creating Secrets from YAML?
  11. Why is putting real secret values in YAML risky?
  12. What are three security controls that help protect Kubernetes Secrets?
Answer key
  1. Non-sensitive configuration such as log level, feature flags, and application mode.
  2. Sensitive values such as passwords, tokens, API keys, and private keys.
  3. Because configuration changes would require rebuilding the image and could make images environment-specific or leak sensitive values.
  4. As environment variables or mounted files.
  5. The data section.
  6. No. Base64 is encoding, not encryption.
  7. Logs are often stored, shipped, indexed, and accessible to more people or systems.
  8. They usually do not change inside the already-running process. The Pod or workload normally needs to restart to receive new environment variable values.
  9. kubectl describe pod <pod-name>, especially the events section.
  10. It lets you write plain text values in the YAML and have Kubernetes store them as Secret data.
  11. Because the file may be committed to Git, copied, shared, or stored insecurely.
  12. Examples: RBAC restrictions, encryption at rest, limiting exec access, external secret managers, avoiding secret logs, and credential rotation.
← Previous: Services Series index Next: Ingress →