
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
You want to run Django on Kubernetes because your application has outgrown single-server deployments or you need the resilience and scaling that only an orchestrator provides. Moving from a traditional VPS or PaaS to K8s introduces complexity around statelessness, static assets, and process management that standard tutorials often gloss over. This guide bridges that gap by focusing on the specific architectural patterns required to make Django production-ready in a containerized environment, drawing on real-world deployments I have managed across AWS EKS and on-premise clusters.
How do you prepare a Django Dockerfile for Kubernetes?
The foundation of any attempt to containerize web applications correctly is the image itself. For Django, a naive Dockerfile leads to bloated images, security vulnerabilities, and slow deployments. You need a multi-stage build that separates dependencies from runtime artifacts.
Multi-stage build strategy
In practice, I always separate the builder stage from the runtime stage. This keeps your final image under 200MB and reduces the attack surface by excluding compilers and development headers. The runtime user should never be root.
# Builder stage
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Runtime stage
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/root/.local/bin:$PATH"
RUN addgroup --system django && \
adduser --system --ingroup django django
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
RUN chown -R django:django /app
USER django
EXPOSE 8000
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"] This pattern ensures that system packages like gcc or libpq-dev used during compilation do not persist in the deployed pod. When you run Django on Kubernetes, smaller images mean faster node scaling and reduced network egress costs.
Why Gunicorn is mandatory
Never run manage.py runserver in a cluster. It is single-threaded, insecure, and lacks process management. Gunicorn (or Uvicorn for async Django) handles concurrent requests and worker recycling. Configure workers based on CPU cores available to the pod, typically (2 x CPU_CORES) + 1. If you are unsure about resource sizing, review Kubernetes resource limits and requests before setting worker counts.
How do you manage Django settings and secrets in Kubernetes?
Hardcoding credentials in Dockerfiles or environment variables defined directly in Deployment specs is a security failure. When operating in regulated environments, treating configuration as code is non-negotiable. Kubernetes offers native primitives for this separation.
ConfigMaps vs Secrets
Use ConfigMaps for non-sensitive data like DJANGO_ALLOWED_HOSTS, log levels, or feature flags. Use Secrets for database passwords, API keys, and the DJANGO_SECRET_KEY. While K8s Secrets are base64-encoded by default, they are not encrypted at rest unless you configure the cluster's encryption provider. For higher security standards, integrate external secret stores.
- ConfigMap: Store
settings.yamlor individual env vars for environment-specific overrides. - Secret: Store
DB_PASSWORDandSECRET_KEY. Mount as files or env vars. - External Secrets Operator: Sync from AWS Secrets Manager, Vault, or Azure Key Vault automatically.
If you are managing sensitive data, follow the patterns in Kubernetes secrets management done right to avoid common pitfalls like committing encoded secrets to Git.
Injecting configuration safely
I prefer mounting secrets as files rather than environment variables where possible, as env vars can leak in crash logs or child processes. However, Django’s os.environ pattern is ubiquitous. If using env vars, ensure your pod security context prevents unauthorized access to /proc/self/environ.
env:
- name: DJANGO_SECRET_KEY
valueFrom:
secretKeyRef:
name: django-secrets
key: secret-key
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: django-db-creds
key: url How do you handle static files when running Django on Kubernetes?
This is the most frequent point of failure for teams migrating Django to K8s. Containers are ephemeral; if Pod A collects static files into its local filesystem, Pod B will not see them. Load balancers will route users randomly, resulting in broken CSS/JS half the time.
Object storage (Recommended)
For most production systems, offloading static and media files to S3, Google Cloud Storage, or MinIO is the correct architectural choice. Use django-storages and WhiteNoise. This makes your Django pods truly stateless and horizontally scalable without shared volume bottlenecks.
Nginx sidecar pattern
If you cannot use external object storage due to compliance or air-gapped constraints, use an init container to collect static files into an emptyDir volume shared with an Nginx sidecar. The Nginx container serves /static/ directly, bypassing Gunicorn entirely. This adds pod overhead but removes external dependencies.
Shared Persistent Volumes
Avoid ReadWriteMany NFS volumes for static files unless absolutely necessary. They introduce latency, single points of failure, and permission headaches. If you must use persistent storage for media uploads, consider Longhorn distributed storage or cloud-native block storage with proper backup policies.
How do you configure readiness probes for Django?
Kubernetes needs to know when your Django app is actually ready to accept traffic. A common mistake is using a simple TCP check on port 8000. The port may be open while the application is still loading models or waiting for database migrations. This causes 502 errors during rolling updates.
Implementing a health endpoint
Create a dedicated view that verifies critical dependencies. Do not put heavy logic here; it runs every few seconds. Return 200 only if the DB connection works and essential caches are reachable.
# views.py
from django.http import JsonResponse
from django.db import connection
def health_check(request):
try:
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
return JsonResponse({"status": "ok"}, status=200)
except Exception as e:
return JsonResponse({"error": str(e)}, status=503) Probe configuration
Set initialDelaySeconds generously for Django apps. Cold starts with many installed apps can take 5–10 seconds. Premature liveness checks will restart your pod in a CrashLoopBackOff. If debugging startup issues, refer to debugging CrashLoopBackOff in Kubernetes.
| Probe Type | Purpose | Django Configuration Tip |
|---|---|---|
| Liveness | Restart stuck pods | Check Gunicorn worker responsiveness, not just DB |
| Readiness | Gate traffic during startup | Verify DB + Cache connectivity; fail fast if missing |
| Startup | Allow slow initialization | Set failureThreshold × periodSeconds > max boot time |
How do you automate Django deployments with GitOps?
Manually applying YAML files with kubectl does not scale and creates audit gaps. In 2026, GitOps is the standard for running Django on Kubernetes reliably. Your desired state lives in Git; a reconciler ensures the cluster matches it.
ArgoCD or Flux
Tools like ArgoCD watch your repository and sync changes automatically. This provides drift detection, rollback capability, and visual deployment tracking. For teams managing multiple environments (staging, production), parameterize manifests using Helm or Kustomize. See setting up GitOps with ArgoCD for implementation details.
Database migration strategy
Migrations are the riskiest part of a Django deployment. Never run them inside the main application container’s entrypoint. If multiple pods start simultaneously, race conditions corrupt your schema. Instead, use a Kubernetes Job that runs before the new deployment rolls out. In ArgoCD, use PreSync hooks; in Helm, use pre-install/pre-upgrade hooks.
# migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: django-migrate
annotations:
argocd.argoproj.io/hook: PreSync
spec:
template:
spec:
containers:
- name: migrate
image: myregistry/django-app:v2.1.0
command: ["python", "manage.py", "migrate", "--noinput"]
envFrom:
- secretRef:
name: django-db-creds
restartPolicy: Never
backoffLimit: 1 Run Django on Kubernetes with Confidence
Successfully operating Django in a cluster requires respecting the framework’s characteristics while adapting to cloud-native constraints. Focus on immutable images, externalized configuration, stateless static file serving, and atomic migrations. These patterns transform Kubernetes from a source of frustration into a reliable platform for growth. Whether you are deploying for a Kathmandu-based startup serving local users or a global SaaS platform, these fundamentals remain constant.
If your team needs help architecting a compliant, observable Django infrastructure or auditing an existing deployment, reach out to discuss your project. Getting the foundation right now prevents costly rework and downtime later.