diff --git a/.dockerignore b/.dockerignore index 89f8ec9d..54e6ef24 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,8 +7,9 @@ LICENSE README.md AUTHORS -# Reinstalled inside the image (alpine-native — avoids a glibc/musl mismatch) -node_modules/ +# Root deps aren't shipped — the image uses .next/standalone's traced +# node_modules. Root-anchored so the nested standalone node_modules is kept. +/node_modules/ # The built .next output is copied in (built on the CI runner, see # build_n_push.yml); only the build cache is excluded — it is not served. diff --git a/docker/Dockerfile b/docker/Dockerfile index f810d967..253a1a67 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,31 +1,30 @@ -FROM node:20-alpine +FROM node:20-slim -# Set working directory WORKDIR /usr/app -# Install PM2 globally -RUN npm install --global pm2 +ENV NODE_ENV=production +# Next's standalone server binds to localhost by default; listen on all +# interfaces inside the container, and pin the port. +ENV HOSTNAME=0.0.0.0 +ENV PORT=3000 + +# Next's standalone output (built on the CI runner, see build_n_push.yml) +# bundles a minimal, traced node_modules plus server.js — there is nothing to +# install here. The output is traced against the runner (Ubuntu/glibc), so this +# runtime image must also be glibc (node:20-slim, NOT alpine/musl) or the traced +# native binaries won't load. +COPY .next/standalone ./ +# standalone does not include static assets or the public dir — copy them in. +COPY .next/static ./.next/static +COPY public ./public + +COPY docker/entrypoint.sh ./entrypoint.sh +RUN chmod +x ./entrypoint.sh -# Copy package.json and package-lock.json before other files -# Utilise Docker cache to save re-installing dependencies if unchanged -COPY ./package*.json ./ - -# Install runtime dependencies (npm ci = clean, reproducible install from the lockfile) -RUN npm ci --omit=dev - -# Copy the app, including the prebuilt .next output. The build now runs on the -# CI runner (see .github/workflows/build_n_push.yml) with a warm .next/cache, -# so there is no `npm run build` step here — we only package the result. -COPY ./ ./ - -COPY /docker/entrypoint.sh ./entrypoint.sh - -RUN chmod u+x /usr/app/entrypoint.sh - -# Expose the listening port EXPOSE 3000 -# apply env variables to the Nextjs .env file +# entrypoint.sh substitutes the APP_NEXT_PUBLIC_DOCSEARCH_* placeholders baked +# into .next with real values from the container env, then execs the CMD. ENTRYPOINT ["/usr/app/entrypoint.sh"] -CMD npm run start \ No newline at end of file +CMD ["node", "server.js"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 9a3c381f..bcb87030 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -7,7 +7,7 @@ set -ex NEXT_PUBLIC_DOCSEARCH_APP_ID=${NEXT_PUBLIC_DOCSEARCH_APP_ID:-"none"} NEXT_PUBLIC_DOCSEARCH_API_KEY=${NEXT_PUBLIC_DOCSEARCH_API_KEY:-"none"} -NEXT_PUBLIC_DOCSEARCH_INDEX_NAME=${NEXT_PUBLIC_DOCSEARCH_INDEX_NAME:"none"} +NEXT_PUBLIC_DOCSEARCH_INDEX_NAME=${NEXT_PUBLIC_DOCSEARCH_INDEX_NAME:-"none"} find /usr/app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#APP_NEXT_PUBLIC_DOCSEARCH_APP_ID#${NEXT_PUBLIC_DOCSEARCH_APP_ID}#g" find /usr/app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#APP_NEXT_PUBLIC_DOCSEARCH_API_KEY#${NEXT_PUBLIC_DOCSEARCH_API_KEY}#g" diff --git a/next.config.mjs b/next.config.mjs index 746b2e24..6acb5aa7 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -16,6 +16,10 @@ const withMDX = nextMDX({ /** @type {import('next').NextConfig} */ const nextConfig = { + // Emit a self-contained server (.next/standalone) with a traced, minimal + // node_modules, so the Docker image ships just the server + static assets + // instead of the full source and dependency tree. + output: 'standalone', assetPrefix: undefined, reactStrictMode: true, pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'],