Summary
Current Dockerfile runs uv sync --locked in a single stage, which installs all dependency groups — including [dependency-groups].dev (mypy, ruff, watchfiles). Prod image ends up carrying dev tooling it will never run.
Changes
Convert to multi-stage:
FROM ghcr.io/astral-sh/uv:python3.14-bookworm-slim AS base
# ... (common setup, copy sources)
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project
COPY . /app
FROM base AS dev
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked
FROM base AS prod
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev
# ... ENV PATH, ENTRYPOINT, CMD
prod is the last stage — default docker build target stays prod. The dev compose overlay (in infra) will pass build.target: dev to pick up dev deps like watchfiles.
Verification
docker build --target dev . → image contains watchfiles, mypy, ruff
docker build . (default, = prod) → image does not contain watchfiles, mypy, ruff
- CI
uv run ruff check / mypy still works (those run on the host, not in the image)
Context
Follow-up to PlaceBrain/infra#4 — watchfiles currently ships in prod images because uv sync installs all groups by default.
Summary
Current
Dockerfilerunsuv sync --lockedin a single stage, which installs all dependency groups — including[dependency-groups].dev(mypy, ruff, watchfiles). Prod image ends up carrying dev tooling it will never run.Changes
Convert to multi-stage:
prodis the last stage — defaultdocker buildtarget stays prod. The dev compose overlay (ininfra) will passbuild.target: devto pick up dev deps likewatchfiles.Verification
docker build --target dev .→ image containswatchfiles,mypy,ruffdocker build .(default, = prod) → image does not containwatchfiles,mypy,ruffuv run ruff check/mypystill works (those run on the host, not in the image)Context
Follow-up to PlaceBrain/infra#4 — watchfiles currently ships in prod images because
uv syncinstalls all groups by default.