Django on Kubernetes: The Complete Production Guide
Deploying Django applications into production Kubernetes clusters requires harmonizing Django's application lifecycle with Kubernetes container orchestration: synchronizing database migrations, establishing robust probe lifecycles, and configuring isolated ports for health monitoring.
Production Deployment Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: django-app
labels:
app: django-app
spec:
replicas: 3
selector:
matchLabels:
app: django-app
template:
metadata:
labels:
app: django-app
spec:
containers:
- name: django
image: my-registry.com/my-django-app:v1.0.0
imagePullPolicy: IfNotPresent
command:
- "python"
- "manage.py"
- "serve"
- "--port=8000"
- "--probe-port=8001"
- "--check-migrations-apply"
- "--req-queue-len=15"
ports:
- name: http
containerPort: 8000
- name: probes
containerPort: 8001
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
startupProbe:
httpGet:
path: /startup
port: probes
initialDelaySeconds: 2
periodSeconds: 2
failureThreshold: 30
timeoutSeconds: 2
livenessProbe:
httpGet:
path: /alive
port: probes
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: probes
periodSeconds: 2
timeoutSeconds: 2
failureThreshold: 2
Kubernetes Service Manifest
apiVersion: v1
kind: Service
metadata:
name: django-app-service
spec:
type: ClusterIP
selector:
app: django-app
ports:
- name: http
port: 80
targetPort: http
- name: metrics
port: 8001
targetPort: probes
Probe Tuning Best Practices
- Always Use a Startup Probe: Without a startup probe, Kubernetes starts evaluating the liveness probe immediately upon container start. If Django takes 10 seconds to load models or apply database migrations, the liveness probe will kill the container before it finishes booting.
- Set a Short Period for Readiness:
Configure
periodSeconds: 2on/readyso Kubernetes immediately sheds load when a worker pool experiences a congestion spike and restores traffic within 2 seconds of recovery. - Isolate Probes with
--probe-port: Running probes on a dedicated port (e.g. 8001) prevents heavy file uploads or long-running queries on port 8000 from starving kubelet health check requests.
Production Best Practices
1. Sizing Workers and Threads: Cost Efficiency vs. Horizontal Scaling
Choosing the optimal worker and thread configuration depends on your cloud economics and scaling strategy:
- Cost-Optimized Strategy (High Node Density):
Running larger Pods with higher concurrency (
--workersand--runtime-threads) is substantially more cost-effective than running dozens of tiny micro-pods. Every additional Pod incurs Kubernetes baseline overhead (kubelet tracking, CoreDNS endpoint registrations, container runtime memory, CNI networking, and sidecar footprints). Sizing--workersto match your container's allocated CPU cores allows a single Pod to handle hundreds of concurrent requests, maximizing throughput per dollar and driving down cloud infrastructure bills. - Horizontal Autoscaling Strategy (Traffic Volatility): If traffic is bursty or highly unpredictable, combine multi-worker Pods with the Horizontal Pod Autoscaler (HPA) to scale out replica counts smoothly under peak traffic waves.
- Dynamic Sizing Defaults:
By default, Granian's Python worker threadpool automatically sizes to
min(32, CPUs + 4). For ASGI mode,--runtime-threadsautomatically scales Tokio event loop threads across all allocated container CPU cores.
2. Zero-Downtime Rolling Deployments (preStop Hook)
When Kubernetes terminates a Pod during a rolling update, the endpoint removal in kube-proxy is asynchronous and takes a few seconds. If the application terminates immediately upon receiving SIGTERM, clients may experience 502 Bad Gateway errors.
Add a simple preStop sleep hook in your container spec to ensure zero-downtime traffic draining:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
This guarantees the Pod is completely removed from all Service and Ingress endpoints before Hurricane initiates its graceful shutdown and finishes in-flight requests.
3. Memory Guardrails vs. Kernel OOM Killer
If a memory leak occurs, the Linux kernel's Out-Of-Memory (OOM) killer abruptly terminates the container with an uncatchable SIGKILL (exit code 137).
To prevent unexpected crashes, set Hurricane's --max-memory parameter to approximately 75-80% of the Pod's cgroup limit:
resources:
limits:
memory: "512Mi"
command:
- "python"
- "manage.py"
- "serve"
- "--max-memory=400"
When worker RSS exceeds 400 MB, Granian initiates a graceful worker respawn without dropping in-flight requests.
4. Horizontal Pod Autoscaling (HPA)
Scale your deployment dynamically using CPU metrics or Hurricane's Prometheus queue backlog:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: django-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: django-app
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 75
Frequently Asked Questions (FAQ)
How should Django database migrations be handled in Kubernetes?
Rather than relying on complex Helm pre-install hooks or fragile initContainers that run unconditionally on every replica, use Hurricane's --check-migrations-apply flag in the container command:
command: ["python", "manage.py", "serve", "--check-migrations-apply"]
This executes pending migrations during container initialization before marking /startup healthy, ensuring no pod receives traffic until database schemas match the deployed code.
Why does Django need a Kubernetes startup probe?
Django applications often perform database migrations, model cache warming, and initial asset checks during boot. If only a liveness probe is configured with short timeouts, Kubernetes will kill the container while it is legitimately applying migrations. A startupProbe with a generous failureThreshold: 60 and periodSeconds: 2 grants up to 120 seconds for initialization while keeping steady-state liveness checks fast (e.g. 5 seconds).
How do you eliminate 502 Bad Gateway errors during rolling updates in Django?
When Kubernetes terminates an old pod, kube-proxy takes 2 to 5 seconds to propagate endpoint removal to the Ingress Controller. If your application terminates immediately upon SIGTERM, clients whose requests are in transit receive 502 Bad Gateway. Adding a lifecycle.preStop hook with sleep 5 ensures the pod stops receiving new ingress traffic before Hurricane finishes in-flight requests and exits.
How should I configure workers and concurrency for Django in Kubernetes?
For optimal cost efficiency, size worker threads (--workers) to match or slightly exceed the CPU cores allocated to your Pod. Running multi-worker concurrency per container maximizes throughput per node and drastically reduces cloud spend by avoiding the CPU and memory tax of spinning up unnecessary extra pods. For horizontal resilience, deploy at least 2–3 multi-worker replicas behind an HPA to ensure high availability during rolling updates.
How do you serve Django static and media files in Kubernetes without an Nginx sidecar?
Hurricane includes built-in static and media file serving directly in its Rust/Granian gateway. Passing --static and --media enables Hurricane to serve assets directly from disk with sub-millisecond latency, completely eliminating the need for an auxiliary Nginx sidecar container in your Pod.
- Production Deployment Manifest
- Kubernetes Service Manifest
- Probe Tuning Best Practices
- Production Best Practices
- Frequently Asked Questions (FAQ)
- How should Django database migrations be handled in Kubernetes?
- Why does Django need a Kubernetes startup probe?
- How do you eliminate 502 Bad Gateway errors during rolling updates in Django?
- How should I configure workers and concurrency for Django in Kubernetes?
- How do you serve Django static and media files in Kubernetes without an Nginx sidecar?
