perf: ship a standalone slim Docker image (~590MB → ~283MB)#841
Conversation
Enable Next `output: 'standalone'` and rewrite the Dockerfile to package only the traced standalone server + .next/static + public on node:20-slim (glibc, matching the runner), running `node server.js`. No in-image npm ci, no full source or dependency tree. Cuts the image ~52%, so docker push and the server pull get much faster.
Also fixes a latent entrypoint bug (${VAR:"none"} -> ${VAR:-"none"}) that busybox sh tolerated but Debian dash (node:20-slim /bin/sh) rejects, which would otherwise crash the container on boot.
Smoke-tested via isolated build + docker run: boots in 37ms, all routes 200, redirects still bundled (/slack-url -> 307), static + public assets served, DocSearch env-injection intact.
📝 WalkthroughWalkthroughThe Docker build was reworked to produce and package a Next.js standalone server output instead of installing dependencies and running npm start inside the image. The base image switched to node:20-slim, .dockerignore was adjusted to preserve traced node_modules, and an entrypoint.sh variable fallback syntax was corrected. ChangesDocker standalone build migration
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant BuildStage
participant Dockerfile
participant Entrypoint
participant NodeServer
BuildStage->>Dockerfile: produce .next/standalone, .next/static, public
Dockerfile->>Dockerfile: COPY standalone artifacts and entrypoint.sh
Dockerfile->>Entrypoint: ENTRYPOINT /usr/app/entrypoint.sh
Entrypoint->>NodeServer: CMD node server.js
NodeServer->>NodeServer: bind HOSTNAME 0.0.0.0 PORT 3000
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
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 `@docker/Dockerfile`:
- Around line 1-30: Add a non-root runtime user to harden the Docker image: the
current Dockerfile based on node:20-slim ends with ENTRYPOINT/CMD but never
switches away from root, so the app still runs as root. Update the Dockerfile
near the existing COPY/ENTRYPOINT setup to use the built-in node user (UID 1000)
with a USER directive, and if any copied artifacts need writable ownership,
adjust the relevant COPY steps to use --chown=node:node so server.js,
.next/static, and public remain accessible.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d4879a20-0383-4492-a88d-df75dbff5296
📒 Files selected for processing (4)
.dockerignoredocker/Dockerfiledocker/entrypoint.shnext.config.mjs
7767ef1 to
10403d5
Compare
Why
Each deploy builds and ships a ~590 MB image carrying the full source and dependency tree. Next's standalone output ships only the traced server + static assets, roughly halving the image so
docker pushand the server pull are much faster. Final PR of the docs build/deploy speed series (Phase C).What
next.config.mjs: enableoutput: 'standalone'.Dockerfile: package only.next/standalone+.next/static+publiconnode:20-slim(glibc, matching the CI runner so the traced native binaries load), runningnode server.js. No in-imagenpm ci, no full source or dependency tree.entrypoint.sh: fix a malformed default expansion (${VAR:"none"}→${VAR:-"none"}). Busyboxsh(alpine) tolerated it; Debiandash(node:20-slim) rejects it, so the base-image change requires the fix..dockerignore: root-anchor/node_modules/so the standalone's tracednode_modulesis kept.Impact
npm ci— the runner build already produces the traced dependency set.Deploy notes
next starttonode server.js(imageCMD). The server compose relies on the image'sCMD/ENTRYPOINT(no overrides), passes theNEXT_PUBLIC_DOCSEARCH_*env the entrypoint substitutes, and maps to container port 3000 — no compose changes required.Summary by CodeRabbit
New Features
Bug Fixes
Chores