diff --git a/Dockerfile b/Dockerfile index 885ea82..6d254dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,16 @@ -FROM node:26-alpine +FROM node:26-alpine AS deps WORKDIR /app -COPY package.json ./ -COPY package-lock.json ./ -RUN npm install +COPY package.json package-lock.json ./ +RUN npm ci + +FROM node:26-alpine AS builder + +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules COPY . . # 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 + +# 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 -CMD ["npm", "run", "start"] +CMD ["node", "server.js"] diff --git a/next.config.ts b/next.config.ts index 66e7bc6..73f3000 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,6 +4,9 @@ import createNextIntlPlugin from "next-intl/plugin" const withNextIntl = createNextIntlPlugin() const nextConfig: NextConfig = { + // Bundles a minimal server with only the needed dependencies, used by the + // Docker image + output: "standalone", images: { remotePatterns: [ {