Manage Django Settings on Kubernetes With ConfigMaps and Secrets
Deliver Django configuration on Kubernetes: ConfigMaps for non-secret settings, Secrets for credentials, envFrom versus mounted files, rolling pods when values change, encryption at rest, and syncing from external secret managers.
Problem statement
A Django image should be the same in staging and production; what differs is configuration: ALLOWED_HOSTS, DATABASE_URL, SECRET_KEY, feature flags, email credentials. On Kubernetes, ConfigMaps hold non-sensitive settings and Secrets hold credentials, both injected as environment variables or files. Common problems: secrets committed to Git as base64 (which is not encryption), pods that keep old values after a change, and every pod getting every secret. A clear structure avoids them.
- Put non-secret settings (
DJANGO_SETTINGS_MODULE,ALLOWED_HOSTS, log level) in a ConfigMap. - Put credentials (
SECRET_KEY,DATABASE_URL, API keys) in a Secret, created from an external manager, SOPS, or Sealed Secrets, never committed in plain base64. - Inject with
envFrom(simple) or mount as files (supports updates without restarts if the app re-reads them). - Trigger a rollout when config changes: hash the config into a pod annotation (Helm
checksum/config) or use Reloader. - Enable encryption at rest for Secrets in the cluster and restrict RBAC so only the app's service account reads them.
- Read settings in Django with typed helpers and fail fast on missing values.
Step-by-step solution
1. ConfigMap and Secret
apiVersion: v1
kind: ConfigMap
metadata: { name: django-config, namespace: app }
data:
DJANGO_SETTINGS_MODULE: config.settings.production
ALLOWED_HOSTS: example.com,www.example.com
LOG_LEVEL: INFO
---
apiVersion: v1
kind: Secret
metadata: { name: django-secrets, namespace: app }
type: Opaque
stringData:
SECRET_KEY: "replace-me"
DATABASE_URL: "postgres://app:replace@db:5432/app?sslmode=require"
stringData accepts plain values and Kubernetes base64-encodes them; base64 is encoding, not encryption, so this manifest must not be committed with real values.
2. Inject into pods
containers:
- name: web
image: ghcr.io/you/myapp:SHA
envFrom:
- configMapRef: { name: django-config }
- secretRef: { name: django-secrets }
Celery and migration Jobs use the same references so every process sees the same configuration. For values that may change without a restart (certificates, rotated tokens), mount Secrets as files instead; Kubernetes updates mounted files (not env vars) after a delay.
3. Roll pods when config changes
Pods do not restart when a ConfigMap or Secret referenced by envFrom changes. Force a rollout:
# Helm deployment template
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
or run kubectl rollout restart deploy/web after changes, or install a controller such as Stakater Reloader that restarts workloads when their referenced config changes.
4. Keep secrets out of Git
Options, from simplest to most integrated:
- SOPS encrypted manifests decrypted in CI or by Flux/Argo CD plugins.
- Sealed Secrets: encrypt with the cluster's public key; only the controller decrypts.
- External Secrets Operator: sync from AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, or Vault.
5. Protect Secrets in the cluster
Enable encryption at rest for Secrets (managed clusters often do by default or with a KMS option), give the app's service account read access only to its own Secrets via RBAC, and avoid granting broad get secrets permissions to CI or humans.
Verification check: kubectl exec deploy/web -- env | grep -c SECRET_KEY returns 1 without printing the value elsewhere, changing a ConfigMap value triggers new pods via your mechanism, Git history contains no plain secrets (gitleaks detect), and the app's service account cannot read other namespaces' Secrets.
Explanation
Kubernetes separates configuration from images through ConfigMaps and Secrets, which pods consume as environment variables or files. Secrets differ mainly in access control and handling (they can be encrypted at rest and are not shown in casual output), not in encoding. Environment variables are read when the process starts, so updates require new pods; files can update live. Sourcing secret values from an external manager or encrypted manifests keeps them out of Git while the cluster provides them to Django at runtime.
Edge cases / notes
- Size limits. ConfigMaps and Secrets are limited to about 1 MiB each.
- Immutable ConfigMaps (
immutable: true) reduce API load and force deliberate rollouts with new names. - Environment variable leaks. Debug pages and crash reports can expose environment; keep
DEBUGoff and scrub reports. - Helm values files with secrets need the same protection as manifests.
Next steps
- Encrypt manifests with Encrypt Django Secrets in Git With SOPS.
- Compare approaches in Manage Django Secrets in Production.
FAQ
Are Kubernetes Secrets encrypted?
Only if encryption at rest is enabled. The base64 in manifests is encoding, not encryption.
Do Django pods pick up changed Secrets automatically?
Not when injected as environment variables. Restart pods, use a checksum annotation, or a reloader controller.
Should Django settings come from ConfigMaps or Secrets?
Non-sensitive values from ConfigMaps; credentials from Secrets.
How do I keep Kubernetes secrets out of Git?
Use SOPS, Sealed Secrets, or the External Secrets Operator with a cloud secret manager.