Real-time Django is a different deploy
A standard Django app runs under WSGI (Gunicorn) and handles request/response. The moment you need WebSockets — live chat, notifications, collaborative editing — you need ASGI and Django Channels. That changes your server, adds a Redis channel layer for cross-process messaging, and introduces connection-affinity concerns. Let's deploy it correctly.
Step 1: ASGI application and routing
Channels wraps your Django app in an ASGI application that routes HTTP to Django and WebSocket to consumers:
# config/asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import chat.routing
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
django_asgi = get_asgi_application()
application = ProtocolTypeRouter({
"http": django_asgi,
"websocket": AuthMiddlewareStack(URLRouter(chat.routing.websocket_urlpatterns)),
})Step 2: A consumer
# chat/consumers.py
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class ChatConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.room = self.scope["url_route"]["kwargs"]["room"]
await self.channel_layer.group_add(self.room, self.channel_name)
await self.accept()
async def receive_json(self, content):
await self.channel_layer.group_send(
self.room, {"type": "chat.message", "text": content["text"]})
async def chat_message(self, event):
await self.send_json({"text": event["text"]})Step 3: The Redis channel layer
Groups and cross-process messaging require a channel layer backed by Redis. Provision a managed Redis on PandaStack and point Channels at it:
# settings.py
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {"hosts": [os.environ["REDIS_URL"]]},
},
}Without Redis, a multi-replica deploy can't broadcast across instances — a message sent to a user on replica A never reaches a user on replica B. The channel layer is what makes horizontal scaling work.
Step 4: Run an ASGI server
Use Daphne (Channels' reference server) or Uvicorn. Bind to the injected port:
# Daphne
daphne -b 0.0.0.0 -p $PORT config.asgi:application
# or Uvicorn
uvicorn config.asgi:application --host 0.0.0.0 --port $PORTFROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD daphne -b 0.0.0.0 -p $PORT config.asgi:applicationStep 5: Wire the managed Postgres
Django's ORM still uses Postgres for persistent data. PandaStack injects DATABASE_URL when you attach a managed PostgreSQL; parse it with dj-database-url:
import dj_database_url
DATABASES = {"default": dj_database_url.config(default=os.environ["DATABASE_URL"])}Run migrations as a release step: python manage.py migrate.
Step 6: Connection affinity matters
WebSocket connections are long-lived. A few deployment realities:
- Ingress must support WebSocket upgrade. PandaStack uses Kong ingress, which handles the HTTP upgrade to
ws:///wss://— yourwss://connections over the custom domain get automatic SSL. - Scale horizontally with the Redis layer, not by relying on a single big instance. The channel layer broadcasts across replicas.
- Avoid scale-to-zero for realtime. Free-tier apps scale to zero on spot nodes, which will drop idle WebSocket connections and cold-start. For production realtime, use a paid tier that stays warm.
Step 7: Deploy
Connect your repo and push:
git push origin mainThe build runs in a rootless BuildKit K8s Job pod, ships to Artifact Registry, and Helm-deploys. Watch the WebSocket handshakes in live logs, and confirm wss://yourdomain/ws/room/ upgrades cleanly.
Step 8: Static files and the HTTP side
Channels still serves your normal Django views over HTTP. Use WhiteNoise or object storage for static files (don't rely on container disk), and collectstatic at build time.
Production checklist
- [ ] ASGI server (Daphne/Uvicorn) bound to
$PORT - [ ] Redis channel layer for cross-replica broadcast
- [ ]
DATABASE_URLinjected; migrations as release step - [ ] Ingress passes WebSocket upgrade (Kong does)
- [ ] Paid/warm tier for realtime (no scale-to-zero)
- [ ] Static files via WhiteNoise or object storage
References
- [Django Channels — Deploying](https://channels.readthedocs.io/en/latest/deploying.html)
- [channels-redis](https://github.com/django/channels_redis)
- [Daphne](https://github.com/django/daphne)
- [Django — ASGI deployment](https://docs.djangoproject.com/en/stable/howto/deployment/asgi/)
---
Realtime Django works beautifully on containers once you've got ASGI plus a Redis channel layer and WebSocket-aware ingress. Deploy Channels on PandaStack with a managed Postgres and Redis — start on the [free tier](https://dashboard.pandastack.io) to prototype, then move to a warm paid tier for production connections.