mirror of
https://github.com/netbirdio/docs.git
synced 2026-10-09 15:19:04 +02:00
perf: ship a standalone slim Docker image (~590MB to ~283MB) (#841)
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.
This commit is contained in:
+3
-2
@@ -7,8 +7,9 @@ LICENSE
|
|||||||
README.md
|
README.md
|
||||||
AUTHORS
|
AUTHORS
|
||||||
|
|
||||||
# Reinstalled inside the image (alpine-native — avoids a glibc/musl mismatch)
|
# Root deps aren't shipped — the image uses .next/standalone's traced
|
||||||
node_modules/
|
# 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
|
# 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.
|
# build_n_push.yml); only the build cache is excluded — it is not served.
|
||||||
|
|||||||
+20
-21
@@ -1,31 +1,30 @@
|
|||||||
FROM node:20-alpine
|
FROM node:20-slim
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /usr/app
|
WORKDIR /usr/app
|
||||||
|
|
||||||
# Install PM2 globally
|
ENV NODE_ENV=production
|
||||||
RUN npm install --global pm2
|
# 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
|
||||||
|
|
||||||
# Copy package.json and package-lock.json before other files
|
# Next's standalone output (built on the CI runner, see build_n_push.yml)
|
||||||
# Utilise Docker cache to save re-installing dependencies if unchanged
|
# bundles a minimal, traced node_modules plus server.js — there is nothing to
|
||||||
COPY ./package*.json ./
|
# 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
|
||||||
|
|
||||||
# Install runtime dependencies (npm ci = clean, reproducible install from the lockfile)
|
COPY docker/entrypoint.sh ./entrypoint.sh
|
||||||
RUN npm ci --omit=dev
|
RUN chmod +x ./entrypoint.sh
|
||||||
|
|
||||||
# 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
|
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"]
|
ENTRYPOINT ["/usr/app/entrypoint.sh"]
|
||||||
|
|
||||||
CMD npm run start
|
CMD ["node", "server.js"]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
set -ex
|
set -ex
|
||||||
NEXT_PUBLIC_DOCSEARCH_APP_ID=${NEXT_PUBLIC_DOCSEARCH_APP_ID:-"none"}
|
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_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_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"
|
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"
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ const withMDX = nextMDX({
|
|||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const 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,
|
assetPrefix: undefined,
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'],
|
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'],
|
||||||
|
|||||||
Reference in New Issue
Block a user