Use a multi-stage Docker build with Next's standalone output

Install with npm ci, ship only the standalone server and its traced
dependencies, and run as a non-root user. Shrinks the image from
1.78 GB to 438 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
leanderandClaude Opus 5.5 committed 2026-10-30 22:39:14 +01:00
1 parent 703ab5a32c
commit b8e352ba22
2 files changed
+32 -5

No files matched your search

+29 -5
View File
@@ -1,11 +1,16 @@
FROM node:26-alpine FROM node:26-alpine AS deps
WORKDIR /app WORKDIR /app
COPY package.json ./ COPY package.json package-lock.json ./
COPY package-lock.json ./ RUN npm ci
RUN npm install
FROM node:26-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
# NEXT_PUBLIC_* values are inlined at build time, and pages are prerendered # NEXT_PUBLIC_* values are inlined at build time, and pages are prerendered
@@ -17,6 +22,25 @@ ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
RUN npm run build RUN npm run build
# Only the standalone server and its traced dependencies end up in the final
# image, not the full node_modules or the source
FROM node:26-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=9000
ENV HOSTNAME=0.0.0.0
# The server writes regenerated pages to .next, so the files must be owned by
# the user it runs as
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
COPY --from=builder --chown=node:node /app/public ./public
USER node
EXPOSE 9000 EXPOSE 9000
CMD ["npm", "run", "start"] CMD ["node", "server.js"]
+3
View File
@@ -4,6 +4,9 @@ import createNextIntlPlugin from "next-intl/plugin"
const withNextIntl = createNextIntlPlugin() const withNextIntl = createNextIntlPlugin()
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
// Bundles a minimal server with only the needed dependencies, used by the
// Docker image
output: "standalone",
images: { images: {
remotePatterns: [ remotePatterns: [
{ {