Saltar a contenido

05 - ConfigMaps and Secrets

What this session is

About 45 minutes. How to inject configuration into pods without baking it into the image - ConfigMaps (non-sensitive) and Secrets (sensitive).

The problem

You build an image once and want to deploy it in dev, staging, prod - each with different config (different database URLs, API keys, log levels). Hardcoding in the Dockerfile breaks that. Hardcoding in the Deployment YAML scatters config across many places.

ConfigMap stores key-value config. Secret stores sensitive key-value config (passwords, API keys, certs). You reference them from Deployments.

ConfigMap

Create from a YAML file:

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: info
  FEATURE_FLAG_A: "true"
  app.properties: |
    server.port=8080
    timeout=30s

Apply:

kubectl apply -f configmap.yaml
kubectl get configmap app-config -o yaml

Alternatively, create from a file or literals:

kubectl create configmap app-config --from-file=app.properties
kubectl create configmap app-config --from-literal=LOG_LEVEL=info --from-literal=PORT=8080

The YAML form is preferred for version-controlled config.

Use a ConfigMap in a Deployment

Two ways: as env vars, or mounted as files.

As env vars:

spec:
  containers:
  - name: app
    image: my-app:1.0
    env:
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: LOG_LEVEL

Or import all keys at once:

spec:
  containers:
  - name: app
    image: my-app:1.0
    envFrom:
    - configMapRef:
        name: app-config

As mounted files:

spec:
  containers:
  - name: app
    image: my-app:1.0
    volumeMounts:
    - name: config-vol
      mountPath: /etc/app
  volumes:
  - name: config-vol
    configMap:
      name: app-config

Each key in the ConfigMap becomes a file. So app.properties is at /etc/app/app.properties and LOG_LEVEL is at /etc/app/LOG_LEVEL.

For an app that reads config from a file (like nginx), mount form is the way.

Secret

Same shape, but for sensitive data:

apiVersion: v1
kind: Secret
metadata:
  name: app-secret
type: Opaque
stringData:
  DATABASE_PASSWORD: hunter2
  API_KEY: xyzzy

stringData accepts plain strings (Kubernetes base64-encodes them internally). The alternative data: field requires you to base64-encode yourself - annoying; prefer stringData.

Apply:

kubectl apply -f secret.yaml
kubectl get secret app-secret -o yaml

Note: when you view a Secret with -o yaml, values appear base64-encoded. Decode:

kubectl get secret app-secret -o jsonpath='{.data.DATABASE_PASSWORD}' | base64 -d

Use Secrets in pods the same way as ConfigMaps:

env:
- name: DATABASE_PASSWORD
  valueFrom:
    secretKeyRef:
      name: app-secret
      key: DATABASE_PASSWORD

Or mount as files. For TLS certs, mounting is the common pattern.

Secrets are not actually that secret

Important caveat: Kubernetes Secrets are not encrypted at rest by default. They're base64-encoded (which is encoding, not encryption - trivially decoded). Anyone with API access to your cluster can read them.

What this means: - For local development: Secrets are fine. - For production: enable encryption at rest (kube-apiserver flag) AND use RBAC to limit who can read Secrets, AND/OR use external secret managers (HashiCorp Vault, AWS Secrets Manager, sealed-secrets, External Secrets Operator).

Treat Kubernetes Secrets as "values I prefer not to log" - not "values I can keep secret from attackers who own the cluster."

A full example: app with config and secret

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: debug
  PORT: "8080"
---
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
type: Opaque
stringData:
  DATABASE_PASSWORD: secret-pass
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 2
  selector:
    matchLabels: {app: app}
  template:
    metadata:
      labels: {app: app}
    spec:
      containers:
      - name: app
        image: my-app:1.0
        envFrom:
        - configMapRef:
            name: app-config
        env:
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secret
              key: DATABASE_PASSWORD
        ports:
        - containerPort: 8080

The --- separates multiple YAML documents in one file. kubectl apply -f handles all three at once.

Updating ConfigMaps and Secrets

If you edit a ConfigMap and re-apply: - For env-injected values: the pods do NOT pick up the new values until they're restarted. (Env vars are set at container start.) - For mounted files: the kubelet updates the files within ~minutes. The app needs to re-read them (or use a config watcher).

So always restart Deployments after config changes:

kubectl rollout restart deployment/app

That triggers a rolling restart that picks up new env values. Pair every ConfigMap/Secret update with a rollout restart for the apps that use them.

Exercise

  1. Apply the full example above (three resources in one file). Check kubectl get cm,secret,deploy.

  2. Inspect:

    kubectl exec deploy/app -- env | grep -E "LOG_LEVEL|PORT|DATABASE"
    
    Should show your config + secret values.

  3. Update the ConfigMap: Edit LOG_LEVEL: info. Apply.

    kubectl exec deploy/app -- env | grep LOG_LEVEL
    
    Still shows the old value. Restart:
    kubectl rollout restart deploy/app
    # wait for rollout
    kubectl exec deploy/app -- env | grep LOG_LEVEL
    
    Now shows new value.

  4. Mount as file: Edit the Deployment to mount the ConfigMap as /etc/config/ instead of env. Apply, restart, exec, ls /etc/config/. Each key is a file.

What you might wonder

"Can I have multiple ConfigMaps for one app?" Yes - list multiple configMapRef or secretRef under envFrom. Useful for splitting "app config" from "feature flags."

"What about typed secrets (TLS, dockerconfigjson)?" Kubernetes has specific types: kubernetes.io/tls for TLS certs, kubernetes.io/dockerconfigjson for image-pull credentials. The type: field distinguishes. Functionally still key-value; the types signal intent and let tools handle them specially.

"Sealed Secrets? External Secrets?" Sealed Secrets (bitnami-labs/sealed-secrets) encrypts a Secret to a public key so it's safe to commit to git. External Secrets Operator pulls from external secret managers and creates K8s Secrets dynamically. Both for production use.

Done

  • Create ConfigMaps from YAML, files, or literals.
  • Create Secrets (using stringData).
  • Inject as env vars (env, envFrom).
  • Mount as files (volumeMounts + volumes).
  • Understand the "restart on config change" pattern.
  • Know Secrets aren't really secret without extra work.

Next: Namespaces →

Comments