Stacks / Python
Preview environments for Python.
FastAPI or Flask with Postgres and a Celery worker.
- Python 3.12
- Postgres 16
- Redis 7
Python previews are straightforward for web-only apps and get interesting the moment you add background workers, ML dependencies, or native extensions. Galley handles the first case out of the box. The second takes one Dockerfile trick.
The config
version: 1
services:
api:
kind: api
build:
path: ./
expose: 8000
depends_on: [postgres, cache]
env:
DATABASE_URL: postgres://app:pw@postgres:5432/app
REDIS_URL: redis://cache:6379/0
health:
path: /healthz
status: 200
worker:
kind: worker
build:
path: ./
start: "celery -A app.tasks worker -l info"
depends_on: [postgres, cache]
env:
DATABASE_URL: postgres://app:pw@postgres:5432/app
REDIS_URL: redis://cache:6379/0
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
The worker reuses the same build (build.path: ./) but overrides the
start command. kind: worker skips the public route — it shouldn’t be
reachable from outside the env.
Migrations: run them on api start (alembic upgrade head && uvicorn ...)
or as a one-shot kind: worker with restart: never.
The usual gotcha
Python base images with ML deps get enormous fast. numpy, torch,
transformers push a base image past three gigabytes. Preview builds
pay this cost every time unless the Dockerfile is structured correctly.
The fix:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Copying requirements.txt (or pyproject.toml + uv.lock, or
poetry.lock) before the rest of the source keeps the install layer
stable across source changes — only the source-copy layer rebuilds when
you push a commit.
For Celery: run the worker as its own service with its own start command. Shipping web + worker in one container via a process manager works, but it hides worker crashes from the dashboard’s per-service state and per-service logs.