feat(build): harden and shrink dockerfile#644
Conversation
|
Changeset detected — the following file(s) will be released with this PR: |
📝 WalkthroughWalkthroughThe repository adds a changeset file, excludes Yarn Berry cache artifacts from Docker context, and rewrites ChangesDistroless Backend Container Build
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant DockerBuild
participant build
participant deps
participant final
DockerBuild->>build: FROM node:${BUILD_IMAGE}
build->>build: install build deps and yarn install --immutable
build->>build: compile backend and extract skeleton/bundle tarballs
build->>deps: provide skeleton artifacts and Yarn state
deps->>deps: yarn workspaces focus --all --production
deps->>deps: prune non-runtime files from node_modules
deps->>final: copy production node_modules
build->>final: copy backend bundle and runtime files
final->>final: run as distroless nonroot image with production env
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
2dc1070 to
8e03d00
Compare
|
Forgot to add, it would be interesting to consider having a dev / quick-start variant image through a Dockerfile.dev or similar so sqlite3 stuff can be pruned too, which should remove another ~30MB. |
LakshanSS
left a comment
There was a problem hiding this comment.
Thank you for your contribution @Pinguladora!
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@Pinguladora Thank you for taking the initiative for this. I noticed that the image uses NodeJs v22.23.0. We experienced a premature close of some network calls related to permission policy in the BE (Backstage Catalog Service to Permission Policy). We pinned the Node version to v22.22 until the fix was released with v22.23.1 (#657). This is only surfaced when Authz is enabled. I see some conflicts in the Dockerfile as well. Please verify the Node version and fix the conflicts too. |
swap to distroless runtime image fix yarn berry cache path tree-shake production node_modules Signed-off-by: Pinguladora <50406923+Pinguladora@users.noreply.github.com>
pruning was too agressive breaking image upon booting explicit Docker Hub path so it cannot be confused or typosquatted in any way Signed-off-by: Pinguladora <50406923+Pinguladora@users.noreply.github.com>
Signed-off-by: Pinguladora <50406923+Pinguladora@users.noreply.github.com>
8e03d00 to
108108d
Compare
|
@kaviththiranga Done. Given the possibility of new regressions on a future, I swapped the distroless image to Minimus (reg.mini.dev/node:v22.23.1) from their community catalog, which has Node minors and patches tagged as opposed to Google's distroless which only tags majors (https://console.cloud.google.com/artifacts/docker/distroless/us/gcr.io) and would require exploration and mapping to SHA manually in case of needing a specific version like v22.22. Size is about the same, ~800MB on my tests, gives the same guarantees as they also use Debian underneath (for some of them) and provide SLSA too (L3 against Google's L2). Furthermore, they respect the upstream bash entrypoint so I dropped the hack for the Helm Chart too. I think this should do it, let me know if there's something else to address. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/backend/Dockerfile`:
- Around line 1-7: The backend Dockerfile’s runtime base image uses
reg.mini.dev/node, but the build workflow only authenticates to ghcr.io, so the
final stage can fail with 401 when pulling the runtime image. Update the Docker
build/auth path to provide credentials for reg.mini.dev or switch the
RUNTIME_IMAGE in the Dockerfile to a registry already accessible in the
workflow, and make sure the change is applied where RUNTIME_IMAGE is defined.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 11db4ce0-1d28-433a-9a6b-007f27b13435
📒 Files selected for processing (3)
.changeset/old-lemons-speak.md.dockerignorepackages/backend/Dockerfile
✅ Files skipped from review due to trivial changes (2)
- .changeset/old-lemons-speak.md
- .dockerignore
Purpose
The current container image (at packages/backend/Dockerfile) produces a ~1.6 GB runtime image that ships the full node:22-bookworm-slim userland (apt, dpkg, g++, libsqlite3-dev, etc.) into the final image even if they aren't necessary. This come with quite a few downsides:
No tracked issue, opened directly from a downstream POC where this surfaced via Trivy reporting ~200 HIGH+CRITICAL findings per Backstage pod, the majority of them in the runtime stage's build toolchain rather than in node_modules.
Goals
Approach
Three-stage build with a distroless final stage:
Distroless's default ENTRYPOINT is ["/nodejs/bin/node"], but the OpenChoreo Helm chart passes args: ["node",
"packages/backend", ...]. To keep that working without requiring a sibling chart change (which ideally should be done regardless, as the chart should be agnostic to image behaviour), the final stage sets ENTRYPOINT [] + ENV
PATH=/nodejs/bin:$PATH so the chart's literal node resolves via PATH. CMD ["node", "packages/backend"] preserves standalone docker run behavior.
Image base pinned by digest in ARG so both the build base and runtime base are reproducible. Images are based on official ones and Google distroless images to align with the rest of OpenChoreo, but they could be swapped by Chainguard, Docker Hardened Images or any other undistro / distroless and should work without much trouble.
User stories
Release note
packages/backend/Dockerfile: shrink from ~1.6 GB to ~855 MB, hardened by switching to a distroless image
Documentation
N/A. Only affects Dockerfile and .dockerignore, no further changes, documentation is self-contained within comments.
Training
N/A
Certification
N/A
Marketing
N/A
Automation tests
N/A
Security checks
Samples
N/A
Related PRs
N/A
Migrations (if applicable)
N/A
Test environment
Learning
Backstage's yarn workspaces focus --all --production is sensitive to BuildKit cache mount paths. Mounting /home/node/.yarn (yarn berry's global cache root) leaks install-state between stages and short-circuits the focus install. Keeping the upstream mount path /app/.yarn/cache (or the inherited /home/node/.cache/yarn) avoids this.
The "conservative" find ... -prune patterns aggressively used by node-prune collide with several packages that use generic directory names (yaml/dist/doc/, @backstage/plugin-app-backend/dist/lib/assets/) at runtime. Final prune list intentionally excludes doc, docs, assets, images, media, website, *.ts, .tsx, tsconfig.json, LICENSE and *.d.ts files, which in particular are read by Backstage's config-loader at startup to validate app-config.yaml.
References used:
Summary by CodeRabbit
.dockerignoreto exclude Yarn Berry cache and install-state artifacts, reducing build context size.