DevOps Tutorial · Containers and Kubernetes · Lesson 21 of 25

Configuration and Secrets

Keep configuration out of images and secrets out of Git: environment variables, ConfigMaps, secret managers and rotation.

Create a free account to save this page, take notes and track your progress. Sign in

Config belongs to the environment

The same image runs in dev, staging and production, so anything that differs between them is configuration supplied at deploy time (the Twelve-Factor App principle): URLs, feature flags, log levels, and secrets.

Required config and defaults
cat > app.sh <<'SCRIPT'
set -euo pipefail
: "${DATABASE_URL:?DATABASE_URL must be set}"     # fail fast if required config is missing
echo "log level: ${LOG_LEVEL:-info}"               # default when unset
echo "connecting to ${DATABASE_URL%%@*}@***"        # never print the full secret
SCRIPT
LOG_LEVEL=debug DATABASE_URL="postgres://app:pw@db:5432/app" bash app.sh
bash app.sh 2>&1 | sed 's/^app.sh: line [0-9]*: //' || true

Output

log level: debug
connecting to postgres://app:pw@***
DATABASE_URL: DATABASE_URL must be set

Run this on your computer — see “Get Started” for setup.

Where configuration lives

Kind Store Examples
Non-secret config Environment variables, Kubernetes ConfigMaps, parameter stores LOG_LEVEL, API_BASE_URL
Secrets A secret manager AWS Secrets Manager or SSM Parameter Store, Azure Key Vault, Google Secret Manager, HashiCorp Vault
Secrets in Kubernetes Secrets synced from the manager (External Secrets Operator, CSI driver) DB passwords, API keys

Rules for secrets

  • Never commit them to Git (scan with gitleaks or GitHub secret scanning; a leaked key must be rotated, since deleting the commit isn't enough).
  • Never bake them into images or print them in CI logs.
  • Prefer no secret at all: workload identity (IAM roles for service accounts, Azure managed identities, GCP Workload Identity) instead of access keys.
  • Rotate automatically and give each service its own credentials.
A tiny pre-commit secret scannerTry it yourself
import re

PATTERNS = {"AWS access key": r"AKIA[0-9A-Z]{16}", "private key": r"-----BEGIN [A-Z ]*PRIVATE KEY-----",
            "password in URL": r"://[^:/\s]+:[^@\s]+@"}
diff = [
    "+LOG_LEVEL=info",
    "+AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE",
    "+DATABASE_URL=postgres://app:[email protected]:5432/app",
]
for line in diff:
    for name, pattern in PATTERNS.items():
        if re.search(pattern, line):
            print(f"BLOCKED ({name}): {line[:30]}...")

Output

BLOCKED (AWS access key): +AWS_ACCESS_KEY_ID=AKIAIOSFODN...
BLOCKED (password in URL): +DATABASE_URL=postgres://app:h...

Test yourself

A database password was committed and pushed, then removed in the next commit. What must you do?

  1. Nothing, it was removed
  2. Rotate the password
  3. Rename the repository
  4. Make the repository private
Show answer

Answer: Rotate the password

The password is still in Git history and anyone who cloned it has it. Treat it as compromised and rotate.

Was this page helpful?