# Build image for the docker export pipeline (Build all, whenever a container engine answers). # # Bakes the repo source + installed deps so every per-site build container runs # the same toolchain and code. The corpus, the shared LMDB index, the .export-index # staging, and the archive cache are bind-mounted READ-ONLY at run time — never # baked (they're hundreds of GB and change constantly). Each container writes only # its per-site output mount (/site). Deploy never runs here; it stays on the host. # The network is left ON at run time (common/publish/build.ts, runDockerBuildOne): # `next build` fetches each site's fonts through next/font/google. # # Entry: docker/build-site.sh runs `archilyzer build site --nodata` # (compose + next build) for one SITE_ID. # # Node and pnpm are pinned to what the workspace runs on. pnpm 11 is not optional: # pnpm-workspace.yaml's `allowBuilds` and `minimumReleaseAgeExclude` are keys # pnpm 9 does not know, and pnpm 11 itself needs Node >= 22.13. bookworm, like # the root Dockerfile's build stage. ARG NODE_IMAGE=node:22.23.2-bookworm-slim FROM ${NODE_IMAGE} ARG PNPM_VERSION=11.26.0 # Archive compressors the export build shells out to. `zip` is the default format; # `tar`/`xz`/`gzip` cover the other configurable archive formats. RUN apt-get update \ && apt-get install -y --no-install-recommends zip tar xz-utils gzip \ && rm -rf /var/lib/apt/lists/* # Install pnpm as a plain global binary (NOT corepack). There is no packageManager # pin in package.json, so corepack would download a pnpm of its own choosing at # run time — in every container, since each runs as an arbitrary host uid with # HOME=/tmp and keeps nothing between runs. A global install is baked once and # needs nothing at run time. RUN npm install -g "pnpm@${PNPM_VERSION}" WORKDIR /repo # Install deps first for layer caching — rebuilds only when a manifest or the # lockfile moves. `allowBuilds` in pnpm-workspace.yaml rebuilds the native # modules (lmdb, msgpackr-extract, esbuild) for this image. COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ COPY common/package.json common/package.json COPY export/package.json export/package.json RUN pnpm install --frozen-lockfile # Only common and export (and the root) are installed — all the export build # needs. Before every `pnpm exec` / `pnpm run`, pnpm 11 checks that the WHOLE # workspace is installed and runs `pnpm install` when it is not, which fails as # the non-root runtime uid (EACCES on /repo). Dockerfile.build.dockerignore # admits no other workspace package, so today the check passes; it is off anyway, # because a builder that reads only the shared .dockerignore bakes all seven and # the first `pnpm exec` dies. The deps are frozen here. The update notice is off # too: every site's log would print it. ENV pnpm_config_verify_deps_before_run=false \ pnpm_config_update_notifier=false # Bake source last so a code change only re-runs from here. The context is # Dockerfile.build.dockerignore's allow-list — root manifests, common/, export/ # (its public/ only the tracked .svg assets) and docker/build-site.sh: about # 7 MB from any checkout, and never a corpus byte. COPY . . # Containers run with `-u ` (so /site outputs are host-owned, not root). # Next writes a couple of fixed-location files into the export package dir # (next-env.d.ts, .next/) and the entrypoint replaces export/public with a link # to the composed /site/public — so export/ must be writable by an arbitrary # runtime uid. Every container is ephemeral (`docker run --rm`) and writes nothing # back but its /site mount, so widening it here is harmless. The layer repeats # export/'s few MB of source, not the image. RUN chmod -R a+rwX /repo/export ENTRYPOINT ["bash", "docker/build-site.sh"]