Skip to content

Shrink the Docker images - #2

Merged
karabayyazilim merged 1 commit into
mainfrom
claude/nifty-cori-kro1vy
Sep 25, 2026
Merged

karabayyazilim merged 1 commit into
mainfrom
claude/nifty-cori-kro1vy

Conversation

@karabayyazilim

@karabayyazilim karabayyazilim commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

The v1.0.0 images carry a lot they never use at runtime. The backend ships the whole Go toolchain next to a single static binary. The frontend ships the full .next directory, which is mostly build cache, plus node_modules with dev dependencies.

Image Download (v1.0.0 → this PR) On disk (v1.0.0 → this PR, approx.)
backend 95 MB → 10 MB 290 MB → 27 MB
frontend 216 MB → 66 MB 830 MB → 190 MB

Download sizes combine the base image layers reported in the CI logs (golang:1.24.4-alpine 83.1 MB, node:18-alpine 44.9 MB → alpine:3.22 3.8 MB, node:22-alpine 60.7 MB) with the app layers, measured by building the same steps locally. On-disk sizes are estimates.

Changes

Backend

  • Runs the static binary on alpine:3.22 instead of golang:1.24.4-alpine. Alpine keeps a shell, because both compose files start the backend with sh -c.
  • Builds with -trimpath -ldflags="-s -w", which takes the binary from 26 MB to 18 MB.
  • Runs as a non-root user (uid 10001). The backend does not write to disk.

Frontend

  • output: 'standalone' in next.config.js. The final stage copies only .next/standalone, .next/static and public, and starts with node server.js. The app layers go from about 700 MB to 22 MB. Of that, 266 MB was .next/cache alone.
  • Installs with npm ci from package-lock.json, which matches package.json, so image builds are reproducible.
  • Node 18 → 22. Node 18 reached end of life in April 2025.
  • Runs as the node user.
  • Adds frontend/.dockerignore, so a host's node_modules and .next never reach the build context. Before, COPY . . could overwrite the dependencies installed in the image with the host's copies.

No compose or deployment changes are needed. The frontend still listens on port 3000, and npm start still works outside Docker; Next.js only prints a warning that node server.js is preferred with standalone output.

Testing

  • CI build: the "Docker images" workflow ran on this branch (run #2). Both images built and were pushed as sha-6f5c956 only; latest is untouched.
  • Built the frontend from a clean copy of the tree (honoring .dockerignore) with the same steps as the Dockerfile, assembled the runtime directory the way the final stage does, and ran node server.js with NODE_ENV=production:
    • All pages return the same status codes as before.
    • All static assets and public/timer-worker.js return 200.
    • The /api rewrite reaches the backend.
    • The full host and guest end-to-end run passes against a local LiveKit server: stage, name tags, ticker, chat and MP4 recording. The only console error was the expected egress-unreachable response from "Go live", since there is no Egress service locally.
  • Built the stripped backend binary the same way and ran it in place of the API. All 34 negative API checks pass.

Not tested yet

  • Running the published images as containers, including as non-root. The backend never writes files, and the frontend's files are owned by node, so no issues are expected. Pulling sha-6f5c956 on a server and starting it once before merging would confirm it.

Backend: run the static binary on plain Alpine instead of the Go
toolchain image, strip debug symbols and run as a non-root user.
Alpine keeps a shell for the compose files that start it with sh -c.

Frontend: build with Next.js standalone output and ship only the
standalone server, its traced dependencies and static assets, instead
of the whole .next directory (mostly build cache) and node_modules
with dev dependencies. Install from package-lock.json with npm ci,
move to Node 22 (Node 18 is end-of-life), run as the node user, and
add a .dockerignore so a host's node_modules and .next never end up
in the build context.

Measured on local builds of the same steps: the backend binary layer
goes from 26 MB to 18 MB and the frontend app layers from about 700 MB
to 22 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@karabayyazilim
karabayyazilim merged commit a4cace1 into main Sep 25, 2026
3 checks passed
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