Skip to content

feat: optimize lampyrid docker image#51

Merged
RadCod3 merged 5 commits into
mainfrom
feat/48-optimize-docker
Jan 12, 2026
Merged

feat: optimize lampyrid docker image#51
RadCod3 merged 5 commits into
mainfrom
feat/48-optimize-docker

Conversation

@RadCod3
Copy link
Copy Markdown
Owner

@RadCod3 RadCod3 commented Jan 12, 2026

This pull request refactors the Docker build process for the project, making the image more secure, efficient, and production-ready. It introduces a multi-stage build using a builder and a minimal distroless runtime image, improves dependency management, and updates the asset path resolution logic to use modern Python packaging practices.

Docker build and runtime improvements:

  • Switched to a multi-stage Docker build with a builder stage based on uv:bookworm-slim for dependency installation and a runtime stage using distroless/cc-debian12:nonroot for enhanced security and minimal footprint.
  • Copied only the necessary Python installation and virtual environment from the builder to the runtime image, reducing the final image size and attack surface.
  • Updated health check and default command to use the distroless image's requirements (no shell) and directly invoke the application entrypoint.

Dependency and environment management:

  • Improved dependency installation by using uv sync with --locked, --no-dev, and --no-editable flags for reproducible, production-ready builds.
  • Set environment variables for UV optimizations and consistent Python directory usage.

Python packaging and asset handling:

  • Refactored get_assets_path in src/lampyrid/utils.py to use importlib.resources.files for accessing asset files, making asset resolution robust and compatible with modern Python packaging standards.

Resolves #48

Add --no-dev flag to uv sync commands and remove uv run wrapper to
prevent runtime syncing. Development tools (ruff, datamodel-code-generator)
are now excluded from the production image, reducing size and eliminating
runtime package downloads.

Changes:
- Added --no-dev flag to both uv sync commands
- Removed uv run from CMD and HEALTHCHECK (use venv directly)
- Prevents runtime re-sync that was downloading dev dependencies
Restructure Dockerfile with separate builder and runtime stages to significantly reduce final image size. Builder stage handles dependency installation and compilation, while runtime stage contains only the minimal artifacts needed to run the application.
Uses bundled assets instead of previous parent.parent style implementation
Replace Alpine runtime with Google Distroless to further reduce image size. Use uv-managed Python installation in builder with optimized environment configuration.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 12, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Chores

    • Optimized Docker build configuration using multi-stage builds, improving container efficiency and reducing image size for deployment.
    • Updated runtime environment and dependency installation process.
  • Refactor

    • Enhanced internal asset resource management.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Restructures the Dockerfile into a multi-stage build using a builder image and distroless runtime base, eliminating UV and build tools from the final image. Updates asset loading in utils.py to use importlib.resources instead of filesystem paths, enabling proper package resource access in containerized environments.

Changes

Cohort / File(s) Summary
Docker Multi-Stage Build Optimization
Dockerfile
Introduces builder stage using uv:bookworm-slim to compile dependencies, then runtime stage using distroless/cc-debian12:nonroot. Copies compiled Python installation and virtual environment from builder to runtime, removing build tools and UV binary. Updates healthcheck and CMD to use direct executable calls instead of uv run.
Asset Loading Refactor
src/lampyrid/utils.py
Removes get_project_root() function and refactors get_assets_path() to use importlib.resources.files() for package-relative asset resolution, replacing filesystem path construction. Adds docstring to updated function.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Add startup verification to CI pipeline #31 — The healthcheck and CMD changes in this PR (from uv run lampyrid to direct lampyrid invocation) may affect CI startup verification that expects the original uv-based command format.

Poem

🐰 A multi-stage dance so neat,
Builder and runtime now complete,
Distroless base keeps image lean,
No UV bloat in between!
Assets dance through importlib's way,
Smaller, faster—hip hooray! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change—optimizing the Docker image with a multi-stage build, distroless runtime, and improved asset handling—which aligns with the primary objective of issue #48.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the Docker multi-stage build, distroless runtime, dependency management improvements, and asset path refactoring with clear reference to issue #48.
Linked Issues check ✅ Passed The PR addresses all major objectives from issue #48: multi-stage build for significant size reduction [#48], excludes dev dependencies [#48], maintains bytecode compilation, uses distroless base image [#48], and refactors asset access [#48].
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue objectives: Docker multi-stage build, dependency optimization, and asset path refactoring via importlib.resources align with issue #48 requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/lampyrid/utils.py:
- Around line 16-19: The package’s assets referenced by get_assets_path (and
used by serve_favicon) are not included in built distributions because
pyproject.toml lacks data-file inclusion; update pyproject.toml to enable
package data (e.g., add project.include-package-data = true or the
backend-specific include settings for uv_build) and, if you need explicit
control, add a MANIFEST.in with a recursive-include for src/lampyrid/assets so
the files under lampyrid/assets are bundled in non-editable builds.
🧹 Nitpick comments (2)
Dockerfile (2)

40-50: Consider adding /python/bin to PATH for completeness.

The Python installation is copied to /python, but only /app/.venv/bin is added to PATH. While the venv's Python executable should work (since UV_LINK_MODE=copy copies the interpreter), adding /python/bin to PATH would ensure any direct Python binary references resolve correctly.

Suggested PATH update
 # Set up the virtual environment in PATH
-ENV PATH="/app/.venv/bin:$PATH"
+ENV PATH="/app/.venv/bin:/python/bin:$PATH"

60-63: Healthcheck verifies import but not server availability.

The current healthcheck confirms the package can be imported, which catches missing dependencies. However, it doesn't verify the server is actually accepting connections on port 3000. Since distroless lacks curl/wget, the current approach is a reasonable compromise.

For a more thorough check, you could use Python to make an HTTP request, though this adds complexity:

Alternative healthcheck (optional)
 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
-    CMD ["python", "-c", "import lampyrid; print('Server can import successfully')"]
+    CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3000/', timeout=5)"]
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74718c9 and 7c00597.

⛔ Files ignored due to path filters (3)
  • src/lampyrid/assets/favicon.ico is excluded by !**/*.ico
  • src/lampyrid/assets/favicon.png is excluded by !**/*.png
  • src/lampyrid/assets/logo-flame.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • Dockerfile
  • src/lampyrid/utils.py
🔇 Additional comments (4)
src/lampyrid/utils.py (1)

22-27: LGTM!

The serve_favicon function correctly handles the case when the asset file doesn't exist by returning a 404 JSON response. The FileResponse usage is appropriate since the package will be filesystem-installed in the Docker image.

Dockerfile (3)

35-38: Excellent choice of distroless base image.

Using gcr.io/distroless/cc-debian12:nonroot is ideal for this use case—it provides minimal attack surface, runs as non-root by default, and includes the C libraries needed by Python. This aligns well with the PR objectives for security and size reduction.


1-33: Well-structured multi-stage build with proper caching.

The build structure follows best practices:

  • Dependencies installed before source code for optimal layer caching
  • --locked ensures reproducible builds from uv.lock
  • --no-dev excludes development dependencies
  • --no-editable ensures package is properly installed (not symlinked)
  • Two-stage uv sync separates dependency installation from project installation

The final CMD ["lampyrid"] correctly uses exec form for distroless compatibility.

Also applies to: 65-66


14-15: Python 3.14 is stable and available as of January 2026 (released October 7, 2025). The version specified in the Dockerfile is valid.

Comment thread src/lampyrid/utils.py
@RadCod3 RadCod3 merged commit 08e4db3 into main Jan 12, 2026
4 checks passed
@RadCod3 RadCod3 deleted the feat/48-optimize-docker branch January 12, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize Docker image size

1 participant