Communitygithub.com

Nuvraxis/nuvraxis-plugins

Dockerfile-Regeln für Next.js Standalone-Apps in einem pnpm + Turborepo-Monorepo – Multi-Stage-Build (Turbo Prune → pnpm Fetch/Offline-Installation → Turbo Build → Minimaler Runner), gepinntes Node 26 Alpine per Digest, checksummen-verifiziertes pnpm-Binary, nicht-root uid 1001, schreibgeschütztes Root-Dateisystem mit beschreibbarem .next/cache, HEALTHCHECK auf /api/health, Build-Args nur für NEXT_PUBLIC_*-Werte, BuildKit-Geheimnisse und Cache-Mounts, Multi-Arch, OCI-Labels und .dockerignore.

Was ist nuvraxis-plugins?

Diese Skill-Anleitung beschreibt die Erstellung optimierter, sicherer und reproduzierbarer Docker-Images für Next.js-Anwendungen in einem pnpm- und Turborepo-Monorepo. Sie definiert einen fünfstufigen Multi-Stage-Build-Prozess: `base`, `pruner`, `deps`, `builder` und `runner`. Besonderer Wert wird auf die Minimierung der Build-Zeit (durch Cache-Wiederverwendung), die Sicherheit (nicht-root Benutzer, schreibgeschütztes Dateisystem, BuildKit-Secrets), die Portabilität (Multi-Arch-Builds) und die Reproduzierbarkeit (gepinnte Node-Version, checksummen-geprüftes pnpm) gelegt. Entwickler, die in CI/CD-Pipelines (GitHub Actions) oder lokal Docker-Images bauen, können dank der detaillierten Regeln und der Fehlerbehebungstabelle typische Probleme wie fehlende CSS/JS, fehlschlagende Health-Checks oder veraltete Lockfiles schnell beheben.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Nuvraxis/nuvraxis-plugins/tree/HEAD/plugins/nuvraxis-frontend/skills/docker-nextjs

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Docker für Next.js

Verwenden für das App-Image. Next-Seite-Standalone-Fakten befinden sich in nextjs-production (references/self-hosting.md); was das Image in Kubernetes ausführt, ist helm-nextjs; wie CI es baut und pusht, ist github-actions.

Das apps/<app>/Dockerfile der Vorlage ist die Quelle der Wahrheit; references/Dockerfile.md enthält es vollständig mit Anmerkungen. Bauen Sie vom Repository-Root aus: docker build -f apps/<app>/Dockerfile -t <app> .

Regeln

  1. Fünf Stufen: base → pruner → deps → builder → runner.

    • base: Node + pnpm, nichts weiter.
    • pruner: turbo prune <app> --docker auf dem vollständigen Repository, sodass nur die App und ihre Workspace-Abhängigkeiten übrig bleiben.
    • deps: Nur out/json/ (Manifeste) und out/pnpm-lock.yaml kopieren, dann pnpm fetch + pnpm install --frozen-lockfile --offline. Diese Schicht wird wiederverwendet, bis sich ein Manifest oder die Lockfile ändert, sodass eine Quellcode-Änderung keine Neuinstallation auslöst.
    • builder: out/full/ (Quellen) kopieren, pnpm exec turbo run build --filter=<app>.
    • runner: Ein frisches Node-Image mit nur dem Standalone-Output. Warum: Quellcode-Änderungen bauen nur die letzten beiden Stufen neu, und der Runner hat kein pnpm, keine Quellen und keine Dev-Abhängigkeiten.
  2. Pinne das Node-Image auf exakte Version und Digest, abgestimmt auf .node-version. node:26.12.0-alpine3.24@sha256:6f08… in einem ARG NODE_IMAGE, verwendet von base und runner. Der Digest ist der Multi-Arch-Index, sodass sowohl amd64 als auch arm64 aufgelöst werden. Warum Alpine statt distroless: Distroless Node veröffentlicht nur fließende Tags (latest, nonroot), sodass die exakte Node-Version nicht mit .node-version abgeglichen werden kann, und es hat kein wget für den HEALTHCHECK. Alpine ist klein, pinnt exakt und sharp liefert vorgebaute musl-Binärdateien, sodass die Bildoptimierung ohne zusätzliche Pakete funktioniert.

  3. Installiere pnpm aus dem GitHub-Release-Binary, verifiziert durch SHA-256. Niemals npm i -g pnpm, niemals Corepack, niemals ein unverifiziertes curl | sh. Ein ARG PNPM_VERSION gleich packageManager, plus ein Prüfsumme pro Architektur für pnpm-linux-{x64,arm64}-musl.tar.gz, ausgewählt durch TARGETARCH. Zum Aktualisieren: Ändern Sie die drei ARGs, entnehmen Sie die sha256-Werte aus den Asset-Digests des Releases (gh release view v<version> --repo pnpm/pnpm --json assets), und aktualisieren Sie packageManager im selben Commit. Fallback, falls sich das Release-Format ändert: Basieren Sie die pruner/deps/builder-Stufen auf ghcr.io/pnpm/pnpm:<version> (kein Node, fügen Sie es hinzu), anstatt eine neue Installationsmethode zu erfinden.

  4. Der Runner kopiert genau drei Dinge.

    COPY --from=builder /repo/apps/<app>/.next/standalone ./
    COPY --from=builder /repo/apps/<app>/.next/static ./apps/<app>/.next/static
    COPY --from=builder /repo/apps/<app>/public ./apps/<app>/public
    

    Standalone enthält weder static noch public; ohne sie liefert die App kein JS oder CSS. Im Monorepo ist der Server apps/<app>/server.js. CMD ["node", "apps/<app>/server.js"], Exec-Form, sodass Node SIGTERM direkt erhält.

  5. Als uid/gid 1001 mit Dateien im Besitz von root ausführen. nextjs:nodejs (1001:1001) erstellen, die kopierten Dateien im Root-Besitz lassen (die App kann lesen, aber ihren eigenen Code nicht ändern) und nur apps/<app>/.next/cache mit chown bearbeiten. USER 1001:1001, numerisch, damit Kubernetes runAsNonRoot ohne Namensauflösung überprüfen kann.

  6. Das Image muss mit einem schreibgeschützten Root-Dateisystem funktionieren. Die einzigen Laufzeitschreibvorgänge gehen an .next/cache (Bildoptimierung, ISR/Fetch-Cache). Kubernetes mountet dort ein emptyDir; lokal verwenden Sie --tmpfs. Testen Sie es:

    docker run --rm -p 3000:3000 --read-only \
      --tmpfs /app/apps/<app>/.next/cache:uid=1001,gid=1001 \
      --cap-drop ALL --security-opt no-new-privileges <app>
    
  7. Setze NODE_ENV=production, HOSTNAME=0.0.0.0, PORT=3000, NEXT_TELEMETRY_DISABLED=1 im Runner. Ohne HOSTNAME=0.0.0.0 bindet sich der Standalone-Server an den Container-Hostnamen und Probes schlagen fehl.

  8. HEALTHCHECK greift auf /api/health mit busybox wget zu. Es ist für docker run/Compose; Kubernetes ignoriert es und verwendet die Probes des Charts auf demselben Pfad.

  9. Build-Args sind nur für Werte, die zur Build-Zeit eingebettet werden: NEXT_PUBLIC_* und NEXT_DEPLOYMENT_ID. NEXT_PUBLIC_* wird in das JS kompiliert, daher muss es ein ARG + ENV im builder sein, und es ist in jeder Umgebung, in der das Image läuft, identisch. Alles, was umgebungsspezifisch ist, ist eine Laufzeit-Umgebungsvariable aus dem Chart. Um eine hinzuzufügen:

    ARG NEXT_PUBLIC_APP_NAME
    ENV NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME}
    

    … plus build-args in beiden Workflows und NEXT_PUBLIC_* ist bereits in turbos env.

  10. Geheimnisse dürfen nie in einem ARG, ENV oder COPY landen. Sie blieben in der Image-Historie. Verwenden Sie einen BuildKit-Secret-Mount, der nur für dieses RUN existiert:

    RUN --mount=type=secret,id=npm_token,env=NPM_TOKEN pnpm fetch   # privates Registry
    

    Der Build-Schritt der Vorlage deklariert bereits das optionale next_server_actions_key-Secret auf diese Weise.

  11. Cache für den pnpm-Store mit einem BuildKit-Cache-Mount. --mount=type=cache,id=pnpm-store,target=/pnpm/store bei fetch/install/dlx, mit pnpm_config_store_dir=/pnpm/store. Der Store überlebt zwischen Builds auf demselben Builder, ohne in einer Schicht zu landen. In CI ist Layer-Caching type=gha (siehe github-actions).

  12. .dockerignore im Repository-Root hält den Kontext klein und Geheimnisse draußen. Es schließt node_modules, .next, .turbo, dist, coverage, .env*, .git, .github, charts aus. Ein Kontext, der node_modules von Windows oder macOS enthält, zerbricht native Pakete im Linux-Image.

  13. Multi-Arch ist linux/amd64,linux/arm64, gebaut von CI. Lokal bauen Sie Ihre eigene Architektur. Jede Stufe ist architekturneutral, außer dem pnpm-Download, der seine Prüfsumme von TARGETARCH wählt.

  14. Labels: Das Dockerfile setzt org.opencontainers.image.title und licenses; CI fügt source, revision, version, created durch docker/metadata-action hinzu. Härten Sie kein Version-Label fest.

  15. Halten Sie Stufen frei von App-spezifischen Hacks. Kein apk add im Runner (die App benötigt kein Shell-Toolset), kein npm, keine node_modules vom Host kopiert. Wenn die App ein natives Tool zur Laufzeit benötigt, begründen Sie dies in einem Kommentar.

Fehlerbehebung

SymptomUrsache
extends: … doesn't resolve während des BuildsEine Datei auf Root-Ebene, die die App benötigt, war nicht im beschnittenen Baum: Verschieben Sie sie in ein Workspace-Paket
ERR_PNPM_OUTDATED_LOCKFILE in depsLockfile nach einer Manifeständerung nicht committet
ERR_PNPM_IGNORED_BUILDSEine neue Abhängigkeit möchte ein Installationsskript: Entscheiden Sie in allowBuilds
App startet, kein CSS/JS.next/static oder public nicht nach apps/<app>/ kopiert
Health-Check schlägt fehl, Logs okayHOSTNAME nicht 0.0.0.0
EROFS zur LaufzeitEtwas schreibt außerhalb von .next/cache; finden Sie es, entfernen Sie nicht --read-only
NEXT_PUBLIC_X leerNicht als Build-Arg übergeben (es wird zur Build-Zeit eingebacken)

Verifizieren

docker build -f apps/<app>/Dockerfile -t <app>:local --build-arg NEXT_DEPLOYMENT_ID=local .
docker run -d --name <app> -p 3000:3000 --read-only --tmpfs /app/apps/<app>/.next/cache:uid=1001,gid=1001 --cap-drop ALL <app>:local
curl -fsS localhost:3000/api/health
docker exec <app> id                     # uid=1001(nextjs)
docker inspect --format '{{.State.Health.Status}}' <app>   # healthy after ~30s
docker rm -f <app>

Quellen

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

Nuvraxis/nuvraxis-plugins

>- Biome 2.5 setup and rules for Nuvraxis monorepos — root biome.json with pinned $schema, VCS ignore, formatter settings, the recommended preset with a11y and the react/next domains, nursery policy, organize imports, overrides, nested per-package configs ("extends": "//"), biome check --write locally, biome ci in CI, VS Code settings, and migrating from ESLint/Prettier. Use when the repo has biome.json or biome.jsonc, @biomejs/biome in package.json, .eslintrc*/eslint.config.*/.prettierrc* to migrate, or when fixing lint and formatting errors.

Nuvraxis/nuvraxis-plugins

>- GitHub Actions conventions for Nuvraxis Next.js apps — exactly three workflows: pr.yaml (every PR to any branch: Biome, stylelint, typecheck/test/build with turbo --affected, Docker build + health check, helm lint + kubeconform; publishes nothing), main.yaml (push to main: one multi-arch image to GHCR tagged sha-SHORTSHA and main, provenance + SBOM, gha cache), release.yaml (tag vX.Y.Z: verify on main, retag the existing image without rebuilding, package and push the Helm chart to OCI, GitHub Release with chart, digest and SBOM). Minimal permissions per job, GITHUB_TOKEN only, pinned action majors. Use when editing .github/workflows/*, adding a CI step, or debugging a failed run or release.

Nuvraxis/nuvraxis-plugins

>- Helm chart rules for Nuvraxis Next.js apps — one chart per app in charts/ (named after the app), published to oci://ghcr.io/nuvraxis/charts and synced by Argo CD; Chart.yaml version/appVersion set only by release.yaml; Deployment with startup/liveness/readiness probes on /api/health, non-root read-only security context, emptyDir for .next/cache, ConfigMap + existing Secret via envFrom, Service, Ingress with cert-manager, HPA, PDB, ServiceAccount, topology spread; no Helm hooks; helm lint --strict + kubeconform. Use when the repo has charts/, Chart.yaml, values*.yaml, templates/*.yaml with Helm syntax, or an Argo CD Application, or when asked to deploy, scale, expose or configure the app on Kubernetes.

Nuvraxis/nuvraxis-plugins

>- Production rules for Next.js 16 App Router apps at Nuvraxis — Server/Client Component boundaries, Cache Components ("use cache", cacheLife, cacheTag, updateTag), Server Actions, route handlers, env handling, instrumentation, output standalone and the /api/health route. Use when the project has "next" in package.json, an app/ or src/app/ directory, next.config.ts, proxy.ts, instrumentation.ts, or files named page.tsx, layout.tsx, route.ts, loading.tsx, error.tsx.

Verwandte Skills