# Docker Dev vs Prod Stage Split

**Applies when:** modifying the backend Dockerfile or docker-compose.yml, debugging import issues in containers, or setting up CI/CD image builds.

- Dockerfile has 4 stages: `base` (runtime only: ffmpeg) -> `deps` (build-essential + Python deps) -> `dev` (compose target) -> `prod` (CI/CD target).
- `base` has only runtime deps (ffmpeg). `deps` adds build-essential for C extension compilation (psycopg2, etc.).
- `dev` inherits from `deps` (has build-essential -- fine for dev). `prod` inherits from `base` (no build-essential) and copies the pre-compiled `.venv` from `deps` via `COPY --from=deps /app/.venv /app/.venv`.
- The `dev` stage does NOT run `uv sync` for the project itself. It relies on `PYTHONPATH=/app` + bind-mounted source at `/app/cpv3`. This avoids the stale editable-install-vs-bind-mount conflict.
- The `prod` stage uses `UV_LINK_MODE=copy` and `uv sync --frozen --no-dev` to create a fully self-contained image with code baked in.
- `prod` stage runs as non-root user `app` (uid/gid 1000). Dev stage stays as root due to bind-mount permission complications.
- `docker-compose.yml` targets the `dev` stage via `build.target: dev`.
- For CI/CD, build the `prod` stage: `docker build --target prod -t cpv3-backend:prod .`
- The `cpv3` project is declared as `source = { editable = "." }` in `uv.lock`. With `UV_LINK_MODE=copy`, uv creates a `.pth` editable finder that maps imports to `/app/cpv3`. In dev, the bind mount overlays this directory, making the installed copy irrelevant but not harmful. The `dev` stage eliminates this ambiguity entirely.
- `watchfiles` CLI (from `uvicorn[standard]`) is used for worker auto-restart: `watchfiles --filter python 'dramatiq ...' /app/cpv3`.
- OrbStack propagates filesystem events natively. Docker Desktop on macOS may need `WATCHFILES_FORCE_POLLING=true`.
- Worker REMOTION_SERVICE_URL was fixed from `http://localhost:8001` to `http://remotion:3001`.
