Conversation
Reviewer's guide (collapsed on small PRs)Reviewer's GuideRefactors the backend Docker build into a multi-stage process that installs dependencies in an intermediate image and copies only the runtime artifacts into a slimmer production image, along with updating metadata and changelog, and touching Docker ignore files. Flow diagram for backend Docker image build and runtime separationflowchart TD
A["Start docker build for backend"] --> B["Build base stage\nFROM python:3.12-slim\nInstall uv\nSet WORKDIR /app\nConfigure dev entrypoint/cmd"]
B --> C["Build build stage\nFROM base"]
C --> D["Copy pyproject.toml and uv.lock* into image"]
D --> E["Run uv sync --frozen --no-dev --no-install-project\nInstall dependencies into virtualenv"]
E --> F["Copy full backend source tree into image"]
F --> G["Write APP_VERSION into VERSION file"]
G --> H["Run uv sync --frozen --no-dev\nInstall project into virtualenv"]
H --> I["Build prod stage\nFROM python:3.12-slim"]
I --> J["Create worker group (gid 1000) and user (uid 1000)"]
J --> K["Set WORKDIR /app and ENV PATH=/app/.venv/bin:$PATH"]
K --> L["Copy /app from build stage\nSet ownership to worker:worker"]
L --> M["Set USER worker and EXPOSE 8000"]
M --> N["Final prod image created\nSlim runtime without uv\nReady for deployment"]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- The final
prodstage no longer defines anENTRYPOINTorCMD, so the runtime container will not start the API as before; consider copying the relevant declarations from thebasestage or explicitly defining them again. - You’re re-running
uv syncin both thebuildstage (with--no-install-project) and again after writingVERSION; if possible, consolidate this into a singleuv syncinvocation to avoid redundant work in the image build.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The final `prod` stage no longer defines an `ENTRYPOINT` or `CMD`, so the runtime container will not start the API as before; consider copying the relevant declarations from the `base` stage or explicitly defining them again.
- You’re re-running `uv sync` in both the `build` stage (with `--no-install-project`) and again after writing `VERSION`; if possible, consolidate this into a single `uv sync` invocation to avoid redundant work in the image build.
## Individual Comments
### Comment 1
<location path="backend/Dockerfile" line_range="34-43" />
<code_context>
+RUN echo "$APP_VERSION" > VERSION && uv sync --frozen --no-dev
+
+# ---- Prod: clean runtime image without uv ----
+FROM python:3.12-slim AS prod
LABEL org.opencontainers.image.source="https://github.com/VEAF/website-2026"
LABEL org.opencontainers.image.description="VEAF Website 2026 - Backend API"
-COPY pyproject.toml uv.lock* ./
-RUN uv sync --frozen --no-dev --no-install-project
+RUN groupadd --gid 1000 worker && useradd --uid 1000 --gid worker --create-home worker
-COPY . .
-RUN echo "$APP_VERSION" > VERSION
-RUN uv sync --frozen --no-dev
+WORKDIR /app
+ENV PATH="/app/.venv/bin:$PATH"
+
+COPY --from=build --chown=worker:worker /app /app
-RUN chown -R worker:worker /app
USER worker
EXPOSE 8000
</code_context>
<issue_to_address>
**issue (bug_risk):** The prod stage no longer inherits ENTRYPOINT/CMD from `base`, so the final image lacks a startup command.
Since `prod` now uses `FROM python:3.12-slim` instead of `FROM base`, it no longer gets the `ENTRYPOINT ["/app/entrypoint.sh"]` and uvicorn `CMD`. The image will build but run without a default startup command, changing behavior from the previous setup.
To preserve the prior behavior, either:
- Define the same `ENTRYPOINT`/`CMD` in the `prod` stage, or
- Introduce a minimal base stage that sets the entrypoint/command and have `prod` inherit from it, keeping `uv` only in the build stage.
</issue_to_address>
### Comment 2
<location path="backend/Dockerfile" line_range="44" />
<code_context>
+WORKDIR /app
+ENV PATH="/app/.venv/bin:$PATH"
+
+COPY --from=build --chown=worker:worker /app /app
-RUN chown -R worker:worker /app
</code_context>
<issue_to_address>
**suggestion:** Copying the entire `/app` tree into prod may undermine the goal of a minimal runtime image.
This `COPY` pulls the entire build context into the runtime image, including build-only artifacts (`uv.lock*`, caches, tests, dev-only files, possibly `.git`, etc., depending on `.dockerignore`). Consider copying only the virtualenv, app code, `entrypoint.sh`, `VERSION`, and other explicit runtime assets to keep the image small, reduce surface area, and make runtime dependencies clearer.
Suggested implementation:
```
# Copy only runtime dependencies from the build stage to keep the image minimal.
# Adjust the paths below to match the layout produced in the build stage.
/* Python virtual environment */
COPY --from=build --chown=worker:worker /app/.venv /app/.venv
/* Application code */
COPY --from=build --chown=worker:worker /app/app /app/app
/* Entrypoint / startup scripts */
COPY --from=build --chown=worker:worker /app/entrypoint.sh /app/entrypoint.sh
/* Version / metadata files */
COPY --from=build --chown=worker:worker /app/VERSION /app/VERSION
```
1. Make sure the paths (`/app/app`, `/app/entrypoint.sh`, `/app/VERSION`) match the actual structure produced by the `build` stage (e.g., if your module is `backend`, you might need `/app/backend` instead of `/app/app`).
2. If there are additional runtime assets (config files, static files, migrations, etc.), add explicit `COPY --from=build` lines for those as well.
3. Ensure `entrypoint.sh` is marked executable in the build or runtime stage (e.g., `RUN chmod +x /app/entrypoint.sh` if not already handled).
4. Confirm `.dockerignore` excludes dev-only and build-only artifacts from the build context so they can’t accidentally leak into other layers.
</issue_to_address>
### Comment 3
<location path="CHANGELOG.md" line_range="12" />
<code_context>
- ADDED login/logout notifications
- FIXED system - customer deploy stack name
- ADDED calandar - planning view (default for mobile)
+- CHANGED system - optimize docker image size
</code_context>
<issue_to_address>
**issue (typo):** Typo: "calandar" should be "calendar".
Please correct the spelling to "calendar" in this changelog entry.
```suggestion
- ADDED calendar - planning view (default for mobile)
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| FROM python:3.12-slim AS prod | ||
|
|
||
| LABEL org.opencontainers.image.source="https://github.com/VEAF/website-2026" | ||
| LABEL org.opencontainers.image.description="VEAF Website 2026 - Backend API" | ||
|
|
||
| COPY pyproject.toml uv.lock* ./ | ||
| RUN uv sync --frozen --no-dev --no-install-project | ||
| RUN groupadd --gid 1000 worker && useradd --uid 1000 --gid worker --create-home worker | ||
|
|
||
| COPY . . | ||
| RUN echo "$APP_VERSION" > VERSION | ||
| RUN uv sync --frozen --no-dev | ||
| WORKDIR /app | ||
| ENV PATH="/app/.venv/bin:$PATH" | ||
|
|
There was a problem hiding this comment.
issue (bug_risk): The prod stage no longer inherits ENTRYPOINT/CMD from base, so the final image lacks a startup command.
Since prod now uses FROM python:3.12-slim instead of FROM base, it no longer gets the ENTRYPOINT ["/app/entrypoint.sh"] and uvicorn CMD. The image will build but run without a default startup command, changing behavior from the previous setup.
To preserve the prior behavior, either:
- Define the same
ENTRYPOINT/CMDin theprodstage, or - Introduce a minimal base stage that sets the entrypoint/command and have
prodinherit from it, keepinguvonly in the build stage.
| WORKDIR /app | ||
| ENV PATH="/app/.venv/bin:$PATH" | ||
|
|
||
| COPY --from=build --chown=worker:worker /app /app |
There was a problem hiding this comment.
suggestion: Copying the entire /app tree into prod may undermine the goal of a minimal runtime image.
This COPY pulls the entire build context into the runtime image, including build-only artifacts (uv.lock*, caches, tests, dev-only files, possibly .git, etc., depending on .dockerignore). Consider copying only the virtualenv, app code, entrypoint.sh, VERSION, and other explicit runtime assets to keep the image small, reduce surface area, and make runtime dependencies clearer.
Suggested implementation:
# Copy only runtime dependencies from the build stage to keep the image minimal.
# Adjust the paths below to match the layout produced in the build stage.
/* Python virtual environment */
COPY --from=build --chown=worker:worker /app/.venv /app/.venv
/* Application code */
COPY --from=build --chown=worker:worker /app/app /app/app
/* Entrypoint / startup scripts */
COPY --from=build --chown=worker:worker /app/entrypoint.sh /app/entrypoint.sh
/* Version / metadata files */
COPY --from=build --chown=worker:worker /app/VERSION /app/VERSION
- Make sure the paths (
/app/app,/app/entrypoint.sh,/app/VERSION) match the actual structure produced by thebuildstage (e.g., if your module isbackend, you might need/app/backendinstead of/app/app). - If there are additional runtime assets (config files, static files, migrations, etc.), add explicit
COPY --from=buildlines for those as well. - Ensure
entrypoint.shis marked executable in the build or runtime stage (e.g.,RUN chmod +x /app/entrypoint.shif not already handled). - Confirm
.dockerignoreexcludes dev-only and build-only artifacts from the build context so they can’t accidentally leak into other layers.
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Summary by Sourcery
Optimize backend Docker image by introducing a multi-stage build with a slim runtime image and updating metadata.
Enhancements:
Build: