Skip to content
Galley

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.