Use RUN --mount for uv to cut image size from 114 MB to 64 MB#437
Conversation
Mount the uv binary from ghcr.io/astral-sh/uv:0.11 during RUN instead of COPY-ing it into a layer. The COPY+delete pattern left a 49.4 MB layer in the image even though uv was removed afterward. The mount approach keeps uv out of all committed layers entirely. Image size: 114 MB → 64 MB. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe pull request refactors Docker build configurations to reduce image size. The main runtime Dockerfile implements multi-stage builds with aggressive Python standard library stripping, switches to Alpine 3.23 base, and expands cleanup steps. The test Dockerfile uses BuildKit mounts to access the Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The 2-stage collapse in PR 3 caused a size regression (45 MB → 114 MB) because stdlib cleanup in a RUN layer can't reclaim space from the base image's Python layer. Restore the intermediate python-deps stage so COPY --from only brings stripped files into the final alpine image. Combined with RUN --mount for uv (avoids 49 MB ghost layer), the final image is 48 MB — smaller than the original 45 MB thanks to the Alpine 3.23 upgrade and Python 3.13 stdlib removals. Image size: 114 MB → 48 MB. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove C headers (/usr/local/include, 2.4 MB), build config (config-3.13-*, 284 KB), REPL enhancements (_pyrepl, 240 KB), decimal fallback (_pydecimal.py, 224 KB), test extension modules (xx*.so, 204 KB), and ~100 unused encoding codepages (~1.2 MB). Keeps only UTF-8, ASCII, Latin-1, UTF-16/32, charmap, IDNA, and unicode escape encodings — all that LC_ALL=C.UTF-8 needs. Image size: 48 MB → 44 MB. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Strip the package manager (apk, libapk, /etc/apk, /lib/apk) and unused openssh binaries (ssh-keyscan, ssh-add, ssh-agent, ssh-pkcs11-helper, ssh-copy-id) plus utility binaries (scanelf, c_rehash, iconv, getconf, ssl_client, findssl.sh) from the final image. The app only needs ssh, scp, and sftp. Prevents installing packages inside a running container and reduces attack surface. Image size: 44 MB → 42.5 MB. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove /etc/ssh/moduli (568 KB) — only needed by sshd (server), not the ssh client. Add .ruff_cache to .dockerignore to exclude 1.3 MB of linter cache from COPY src/python/. Image size: 42.5 MB → 41.5 MB. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add .pytest_cache, pyrightconfig.json to .dockerignore. Remove pyproject.toml and uv.lock from /app/python/ after COPY (they're needed in the python-deps stage but not at runtime). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The typings/ directory contains .pyi type stubs used only by Pyright during development. Not needed at runtime. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/docker/build/docker-image/Dockerfile`:
- Line 17: Change the floating Alpine tag used in the build stage to the
explicit alpine3.23 variant to match the final stage: replace the FROM line that
reads "FROM python:3.13-alpine" with "python:3.13-alpine3.23" and do the same
for the second occurrence later in the Dockerfile (the second builder stage
around line 72) so both builder stages are pinned to Alpine 3.23 and avoid
future musl/alpine mismatches.
In `@src/docker/build/test-image/Dockerfile`:
- Around line 6-8: The RUN line invoking the vendored uv pip installer is
passing --group test without qualifying the target pyproject, so the test group
from /tmp/pyproject.toml is not being installed; update the uv pip install
invocation in the RUN step (the line that starts with "/tmp/uv pip install") to
qualify the pyproject, e.g. use "--group /tmp/pyproject.toml:test" or switch to
"--project /tmp --group test" so the test dependencies from /tmp/pyproject.toml
are installed correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: dd0f53b0-671e-4260-8c40-a3140d768ea4
📒 Files selected for processing (3)
src/docker/build/docker-image/Dockerfilesrc/docker/build/docker-image/Dockerfile.dockerignoresrc/docker/build/test-image/Dockerfile
|
|
||
| # Install Python dependencies with uv, then clean up | ||
| COPY --from=ghcr.io/astral-sh/uv:0.11 /uv /usr/local/bin/uv | ||
| FROM python:3.13-alpine AS python-deps |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "Dockerfile" -path "*/docker/build/docker-image/*"Repository: nitrobass24/seedsync
Length of output: 107
🏁 Script executed:
cat -n src/docker/build/docker-image/DockerfileRepository: nitrobass24/seedsync
Length of output: 6703
🌐 Web query:
python:3.13-alpine3.23 docker hub tag availability
💡 Result:
Yes, the python:3.13-alpine3.23 tag is available on Docker Hub for the official Python Docker image. It is based on Alpine Linux 3.23 and uses Python 3.13.13. You can pull it with docker pull python:3.13-alpine3.23. This tag is explicitly listed among supported tags on the official Docker Hub page, has dedicated image layer details pages, and a corresponding Dockerfile in the docker-library/python repository at path 3.13/alpine3.23/Dockerfile.
Citations:
- 1: https://hub.docker.com/_/python
- 2: https://hub.docker.com/layers/library/python/3.13-alpine3.23/images/sha256-1917b1a36e6d6d190bb594d3c41349623080cfe4bf485baec54e1378fd2d94ae
- 3: https://github.com/docker-library/python/blob/ced4ac7ca9f8f8bdbb113f06fe02c42895875aa4/3.13/alpine3.23/Dockerfile
- 4: https://hub.docker.com/layers/library/python/3.13-alpine3.23/images/sha256-9e28339897646e00f176fbc1694e444b56d98a332bda2d8d080da96dd45112eb
- 5: https://hub.docker.com/_/python/?tab=tags
- 6: https://hub.docker.com/_/python/tags
- 7: https://github.com/docker-library/official-images/blob/master/library/python
- 8: https://hub.docker.com/_/python/tags?name=alpine
🌐 Web query:
python docker official image alpine3.23 tag variants 3.13
💡 Result:
The official Python Docker image provides Python 3.13 on Alpine 3.23 via the following tags (latest as of 2026-05-01, pushed 4 days ago): - python:3.13.13-alpine3.23 - python:3.13-alpine3.23 (aliases to the above) These are the standard "alpine" variants. There are no additional sub-variants (e.g., slim-alpine) specifically for Alpine 3.23 with Python 3.13; the official variants are primarily distinguished by base OS/distribution (Debian full/slim, Alpine, Windows Server Core). The image is based on alpine:3.23, builds Python 3.13.13 from source (verified SHA256 checksum), includes pip/ensurepip, and supports multiple architectures (amd64, arm/v6/v7/64, 386, ppc64le, riscv64, s390x). Compressed size ~16MB per arch. To pull: docker pull python:3.13-alpine3.23 Source Dockerfiles: https://github.com/docker-library/python/tree/master/3.13/alpine3.23
Citations:
- 1: https://hub.docker.com/_/python/tags?name=3.13-alpine&page=1
- 2: https://hub.docker.com/_/python
- 3: https://github.com/docker-library/python/blob/ced4ac7ca9f8f8bdbb113f06fe02c42895875aa4/3.13/alpine3.23/Dockerfile
- 4: https://github.com/docker-library/official-images/blob/master/library/python
- 5: https://www.rust-lang.org
Pin the builder to alpine3.23 explicitly to prevent Alpine/musl version mismatch on future rebuilds.
python:3.13-alpine is a floating tag that currently aliases to alpine3.23. The final stage is pinned to alpine:3.23, but leaving the builder on the floating alias risks an Alpine/musl mismatch when Docker's shared tag moves to a future version. Use python:3.13-alpine3.23 to keep both stages coupled to the same Alpine version.
Also applies to line 72.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/docker/build/docker-image/Dockerfile` at line 17, Change the floating
Alpine tag used in the build stage to the explicit alpine3.23 variant to match
the final stage: replace the FROM line that reads "FROM python:3.13-alpine" with
"python:3.13-alpine3.23" and do the same for the second occurrence later in the
Dockerfile (the second builder stage around line 72) so both builder stages are
pinned to Alpine 3.23 and avoid future musl/alpine mismatches.
| RUN --mount=from=ghcr.io/astral-sh/uv:0.11,source=/uv,target=/tmp/uv \ | ||
| /tmp/uv pip install --system --no-cache --strict \ | ||
| -r /tmp/pyproject.toml --group test \ |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "Dockerfile" -path "*/test-image/*" | head -5Repository: nitrobass24/seedsync
Length of output: 105
🏁 Script executed:
cat -n src/docker/build/test-image/Dockerfile | head -20Repository: nitrobass24/seedsync
Length of output: 740
🌐 Web query:
uv pip install --group documentation astral-sh uv pyproject.toml
💡 Result:
The command uv pip install --group documentation astral-sh uv pyproject.toml installs the astral-sh/uv package (and potentially others specified) into the current virtual environment, sourcing the dependency group named "documentation" from the pyproject.toml file in the current directory. This works if your local pyproject.toml defines a [dependency-groups.documentation] section with astral-sh/uv (or other packages) listed as dependencies. For example: [dependency-groups] documentation = ["astral-sh/uv"] The --group documentation flag tells uv pip install to include dependencies from that named group in the current project's pyproject.toml. The pyproject.toml argument specifies the file containing the group definition (defaults to ./pyproject.toml if omitted). Packages like astral-sh/uv refer to the uv package from the Astral GitHub repository. Note: --group only applies to groups from the specified/local pyproject.toml; it does not affect other install sources. This is distinct from uv add --group documentation astral-sh/uv, which would add the package to the group in pyproject.toml itself. To use: Ensure you're in a uv-managed environment (uv venv or project dir), then run the command. It will resolve and install the locked versions from uv.lock if present.
Citations:
- 1: https://docs.astral.sh/uv/guides/migration/pip-to-project/
- 2: https://docs.astral.sh/uv/pip/packages/
- 3: https://docs.astral.sh/uv/reference/cli/
- 4: https://docs.astral.sh/uv/concepts/projects/dependencies/
- 5: https://til.simonwillison.net/uv/dependency-groups
- 6: https://github.com/astral-sh/uv/blob/main/pyproject.toml
🌐 Web query:
uv pip install -r flag requirements file group option
💡 Result:
To install packages from a requirements file using uv's pip-compatible interface, use uv pip install -r requirements.txt. This works exactly like pip install -r requirements.txt. The --group flag in uv pip install installs dependencies from a specified dependency group in a pyproject.toml file (PEP 735), not from the requirements file. Syntax: --group [path:]name, e.g., uv pip install --group dev installs the dev group from the current pyproject.toml, or uv pip install --group other/pyproject.toml:dev for a specific file. Important: --group does not apply to requirements files passed with -r. They are independent: uv pip install -r requirements.txt --group dev installs from both sources separately. If no pyproject.toml or group exists, --group will error. For project-managed dependency groups (preferred in uv), use uv sync --group dev instead of uv pip install. This feature was added in early 2025 for pip compatibility (sources confirm implementation and docs).
Citations:
- 1: https://git.joshthomas.dev/mirrors/uv/commit/ba732311641f6eeda8a2f09fb83ac1b81a5e17db?files=docs%2Freference%2Fcli.md
- 2:
uv pip install --groupfails wherepip install --groupsucceeds astral-sh/uv#13138 - 3: https://docs.astral.sh/uv/pip/compile/
- 4: https://docs.astral.sh/uv/reference/cli/
- 5: https://docs.astral.sh/uv/pip/packages/
- 6: Install dependency groups in the
uv pipinterface, i.e.,uv pip install --groupastral-sh/uv#8590
🌐 Web query:
uv pip install pyproject.toml vs -r requirements.txt difference
💡 Result:
The command "uv pip install pyproject.toml" installs dependencies listed directly in a pyproject.toml file using uv's pip-compatible interface, treating the file as a requirements source similar to requirements.txt. However, this is not the recommended uv workflow for projects. Key differences: - uv pip install -r requirements.txt: Installs packages from a flat list in requirements.txt into the current environment (requires existing venv). requirements.txt typically contains pinned versions (e.g., from pip freeze or pip compile) and lacks project metadata like Python version or optional deps. - uv pip install pyproject.toml: Similar, but reads deps from [project.dependencies] or [dependency-groups] in pyproject.toml. Still pip-style, no lockfile integration, no automatic project env management. Recommended uv project workflow (superior to both): - Use "uv sync" in a directory with pyproject.toml (+ uv.lock): Locks deps to exact versions in uv.lock, installs into .venv, syncs environment automatically. Handles metadata, dev deps, Python version. Run scripts with "uv run". - To add deps: "uv add pkg" (updates pyproject.toml + lockfile + env). - Migrate from requirements.txt: "uv init", then "uv add -r requirements.txt". pyproject.toml is for project metadata/declarative deps; requirements.txt is legacy pinned list. uv prefers pyproject.toml + uv.lock for reproducibility and speed. "uv pip install pyproject.toml" works but bypasses uv project features like auto-sync and locking.
Citations:
- 1: https://docs.astral.sh/uv/guides/projects
- 2: https://docs.astral.sh/uv/guides/migration/pip-to-project/
- 3: https://pydevtools.com/blog/requirementstxt-vs-pyprojecttoml/
- 4: https://pydevtools.com/handbook/how-to/migrate-requirements.txt/
- 5: https://pydevtools.com/handbook/reference/uv/index.md
- 6: https://docs.astral.sh/uv/concepts/projects/layout/
- 7: https://docs.astral.sh/uv/guides/projects/
- 8: http://docs.astral.sh/uv/concepts/projects/dependencies/
- 9: https://docs.astral.sh/uv/concepts/projects/sync/
--group test is targeting the wrong project file.
With uv pip install, --group does not apply to the source passed via -r; it must be qualified with a path to specify the target pyproject.toml file. In this step, the test group is not being sourced from /tmp/pyproject.toml. Use --group /tmp/pyproject.toml:test or --project /tmp --group test to install the test dependencies.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/docker/build/test-image/Dockerfile` around lines 6 - 8, The RUN line
invoking the vendored uv pip installer is passing --group test without
qualifying the target pyproject, so the test group from /tmp/pyproject.toml is
not being installed; update the uv pip install invocation in the RUN step (the
line that starts with "/tmp/uv pip install") to qualify the pyproject, e.g. use
"--group /tmp/pyproject.toml:test" or switch to "--project /tmp --group test" so
the test dependencies from /tmp/pyproject.toml are installed correctly.
Add to python-deps cleanup: _tkinter.so (tkinter dir was removed but not the .so), _xxtestfuzz.so (test module), _lsprof.so (profiler), _interp*.so (sub-interpreters), and venv/ directory. Add /usr/lib/libapk.so* to runtime cleanup (missed in apk removal). Image size: 41.3 MB → 40.9 MB. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Three optimizations to fix the size regression from PR #436 and further reduce image size:
Restore 3-stage build — The 2-stage collapse caused a regression (45 MB → 114 MB) because
rm -rfin aRUNlayer can't reclaim space from the base image's Python layer. The intermediatepython-depsstage ensuresCOPY --fromonly brings stripped files into the finalalpine:3.23image.Use
RUN --mountfor uv — Mount the uv binary duringRUNinstead ofCOPY-ing it into a layer. The oldCOPY+rmpattern left a 49 MB ghost layer even though uv was deleted afterward. Applied to both production and test-image Dockerfiles.Strip additional Python artifacts — Remove C headers (2.4 MB), build config (284 KB), REPL enhancements (240 KB), decimal fallback (224 KB), test extension modules (204 KB), and ~100 unused encoding codepages (~1.2 MB).
Image size: 114 MB → 44.3 MB (was 45 MB before PR #436)
Test plan
make buildbuilds successfully🤖 Generated with Claude Code
Summary by CodeRabbit