Skip to content

perf: ship a standalone slim Docker image (~590MB → ~283MB)#841

Merged
SunsetDrifter merged 1 commit into
mainfrom
docs/standalone-image
Jul 9, 2026
Merged

perf: ship a standalone slim Docker image (~590MB → ~283MB)#841
SunsetDrifter merged 1 commit into
mainfrom
docs/standalone-image

Conversation

@SunsetDrifter

@SunsetDrifter SunsetDrifter commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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 push and the server pull are much faster. Final PR of the docs build/deploy speed series (Phase C).

What

  • next.config.mjs: enable output: 'standalone'.
  • Dockerfile: package only .next/standalone + .next/static + public on node:20-slim (glibc, matching the CI runner so the traced native binaries load), running node server.js. No in-image npm ci, no full source or dependency tree.
  • entrypoint.sh: fix a malformed default expansion (${VAR:"none"}${VAR:-"none"}). Busybox sh (alpine) tolerated it; Debian dash (node:20-slim) rejects it, so the base-image change requires the fix.
  • .dockerignore: root-anchor /node_modules/ so the standalone's traced node_modules is kept.

Impact

  • Image ~590 MB → ~283 MB (−52%): faster registry push and server pull.
  • Removes the in-image npm ci — the runner build already produces the traced dependency set.

Deploy notes

  • Runtime command changes from next start to node server.js (image CMD). The server compose relies on the image's CMD/ENTRYPOINT (no overrides), passes the NEXT_PUBLIC_DOCSEARCH_* env the entrypoint substitutes, and maps to container port 3000 — no compose changes required.

Summary by CodeRabbit

  • New Features

    • Switched the app’s container build to use a prebuilt standalone server output, making the runtime image smaller and simpler.
    • Updated container settings so the app binds correctly inside Docker.
  • Bug Fixes

    • Fixed the fallback handling for an optional environment variable so it now defaults reliably when unset.
  • Chores

    • Updated ignore rules and build configuration to better match the standalone deployment setup.

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.
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The 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.

Changes

Docker standalone build migration

Layer / File(s) Summary
Enable standalone output
next.config.mjs
Adds output: 'standalone' with comments explaining the self-contained server build.
Rebuild Dockerfile around standalone artifacts
docker/Dockerfile
Switches to node:20-slim, sets WORKDIR, env vars (NODE_ENV, HOSTNAME, PORT), copies .next/standalone, .next/static, public, and entrypoint.sh, then runs via entrypoint and node server.js instead of npm ci/npm run start.
Supporting ignore and entrypoint fixes
.dockerignore, docker/entrypoint.sh
Root-anchors the /node_modules/ ignore rule with explanatory comments, and fixes the NEXT_PUBLIC_DOCSEARCH_INDEX_NAME default-value shell syntax to ${VAR:-"none"}.

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
Loading

Poem

A rabbit hopped through Docker's den,
Trimmed the fat, packed standalone again,
node_modules traced, slim as can be,
"none" now falls back correctly,
Thump thump — ship it, friends! 🐇📦

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: switching to a standalone, slimmer Docker image with a major size reduction.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/standalone-image

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 76845ec and 10403d5.

📒 Files selected for processing (4)
  • .dockerignore
  • docker/Dockerfile
  • docker/entrypoint.sh
  • next.config.mjs

Comment thread docker/Dockerfile
@SunsetDrifter SunsetDrifter force-pushed the docs/standalone-image branch from 7767ef1 to 10403d5 Compare July 9, 2026 14:45
@SunsetDrifter SunsetDrifter merged commit 44cab34 into main Jul 9, 2026
7 checks passed
@SunsetDrifter SunsetDrifter deleted the docs/standalone-image branch July 9, 2026 14:51
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.

2 participants