Stacks / Django
Preview environments for Django.
Django with Postgres, Celery, and Redis.
- Django 5
- Postgres 16
- Redis 7
- Celery
Django previews work well, provided you get two settings right:
ALLOWED_HOSTS and SECRET_KEY. Everything else is muscle memory from
docker-compose.
The config
version: 1
services:
web:
kind: web
build:
path: ./
start: "gunicorn myapp.wsgi --bind 0.0.0.0:8000 --workers 2"
expose: 8000
depends_on: [postgres, cache]
env:
DATABASE_URL: postgres://app:pw@postgres:5432/app
REDIS_URL: redis://cache:6379/0
DEBUG: "0"
ALLOWED_HOSTS: ".preview.yourco.dev"
SECRET_KEY: "${DJANGO_SECRET_KEY}"
worker:
kind: worker
build:
path: ./
start: "celery -A myapp worker -l info"
depends_on: [postgres, cache]
env:
DATABASE_URL: postgres://app:pw@postgres:5432/app
REDIS_URL: redis://cache:6379/0
SECRET_KEY: "${DJANGO_SECRET_KEY}"
postgres:
kind: database
image: postgres:16-alpine
expose: 5432
env:
POSTGRES_USER: app
POSTGRES_PASSWORD: pw
POSTGRES_DB: app
cache:
kind: cache
image: redis:7-alpine
expose: 6379
DJANGO_SECRET_KEY is a project-level env var managed in the dashboard
(Settings → Environment vars, mark it secret: true so it’s
encrypted under the master key). Galley interpolates it into both the
web and worker envs at deploy time.
Migrations: run on container start (python manage.py migrate && gunicorn ...) or as a one-shot kind: worker with restart: never.
The usual gotcha
Django fails closed on ALLOWED_HOSTS mismatch. A preview served at
pr-42-myapp.preview.yourco.dev returns 400 Bad Request with a cryptic
“Invalid HTTP_HOST header” if ALLOWED_HOSTS doesn’t match. The error
message never mentions the setting.
The leading-dot pattern matches any subdomain:
ALLOWED_HOSTS = [".preview.yourco.dev"]
Don’t ship that to production — real ALLOWED_HOSTS values belong in
your prod config. For belt-and-braces, gate it on a deploy-time variable
your prod config doesn’t set, and only enable the wildcard there.