Blog

ConfigMaps and Secrets: managing config in Kubernetes

Hardcoding config values into your container image is a bad idea. ConfigMaps and Secrets are how Kubernetes separates configuration from code.

Article info

Hardcoding config into your container image is a problem. Change a database URL and you have to rebuild the image. Commit a password into a YAML file and it ends up in version control. Kubernetes solves both with two dedicated resource types: ConfigMaps for regular config and Secrets for sensitive data.

ConfigMaps

A ConfigMap stores key-value pairs that your Pods can read at runtime. Environment variables, config files, command-line arguments — anything that changes between environments belongs here, not in the image.

Creating a ConfigMap

configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: production
LOG_LEVEL: info
MAX_CONNECTIONS: "100"
apply-configmap
kubectl apply -f configmap.yaml
kubectl get configmap app-config
kubectl describe configmap app-config

Using a ConfigMap as environment variables

pod-with-configmap-env.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
  - name: my-app
    image: my-app:1.0
    envFrom:
      - configMapRef:
          name: app-config

envFrom pulls all keys from the ConfigMap and injects them as environment variables. Your app reads APP_ENV, LOG_LEVEL, and MAX_CONNECTIONS like any other env var.

If you only need specific keys:

pod-with-configmap-specific.yaml
env:
- name: LOG_LEVEL
  valueFrom:
    configMapKeyRef:
      name: app-config
      key: LOG_LEVEL

Using a ConfigMap as a mounted file

Sometimes your app expects a config file, not env vars. You can mount a ConfigMap as a volume:

configmap-as-file.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
nginx.conf: |
  server {
    listen 80;
    location / {
      root /usr/share/nginx/html;
    }
  }
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
  - name: nginx
    image: nginx:1.27
    volumeMounts:
      - name: config-volume
        mountPath: /etc/nginx/conf.d
volumes:
  - name: config-volume
    configMap:
      name: nginx-config

The file nginx.conf shows up inside the container at /etc/nginx/conf.d/nginx.conf.

Secrets

Secrets work like ConfigMaps but are meant for sensitive data — passwords, API keys, tokens, certificates. Kubernetes stores them base64-encoded and handles them with a bit more care (they’re not shown in logs, can be restricted by RBAC, etc.).

Base64 is not encryption — it’s just encoding. Don’t treat Secrets as fully secure without additional tooling like Sealed Secrets or Vault.

Creating a Secret

secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
DB_USER: myuser
DB_PASSWORD: supersecretpassword

stringData lets you write plain text — Kubernetes handles the base64 encoding. Apply it the same way:

apply-secret
kubectl apply -f secret.yaml

# List secrets (values are hidden)
kubectl get secret db-credentials

# Decode a value if you need to inspect it
kubectl get secret db-credentials -o jsonpath='{.data.DB_PASSWORD}' | base64 --decode

Using a Secret as environment variables

pod-with-secret.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
  - name: my-app
    image: my-app:1.0
    env:
      - name: DB_USER
        valueFrom:
          secretKeyRef:
            name: db-credentials
            key: DB_USER
      - name: DB_PASSWORD
        valueFrom:
          secretKeyRef:
            name: db-credentials
            key: DB_PASSWORD

ConfigMap vs Secret — when to use which

ConfigMapSecret
Database URLsyesno
Passwords, API keysnoyes
Feature flagsyesno
TLS certificatesnoyes
Log levels, timeoutsyesno

The rule of thumb: if you’d be uncomfortable committing it to git, use a Secret.

One thing to watch out for

Kubernetes does not automatically restart your Pods when a ConfigMap or Secret changes. If you update a ConfigMap and need the change to take effect, you have to roll the Deployment:

rollout-restart
kubectl rollout restart deployment/my-app

This creates new Pods with the updated config while gracefully terminating the old ones.

Keeping config out of your image

The point of all this is separation. Your container image should be the same artifact whether it runs in dev, staging, or production. The environment-specific stuff — URLs, feature flags, credentials — lives in ConfigMaps and Secrets, injected at runtime. Change config without rebuilding. Rotate a secret without touching the image.

Next: Namespaces — how to organize all of this when you have more than one team or environment in the same cluster.