Communitygithub.com

Nuvraxis/nuvraxis-plugins

Reglas de Dockerfile para aplicaciones Next.js standalone en un monorrepositorio pnpm + Turborepo: compilación multi-etapa, endurecimiento de seguridad y optimización de imágenes.

¿Qué es nuvraxis-plugins?

Esta habilidad proporciona un Dockerfile de múltiples etapas altamente optimizado y endurecido para aplicaciones Next.js standalone dentro de un monorrepositorio pnpm + Turborepo. Utiliza una estrategia de cinco etapas (`base`, `pruner`, `deps`, `builder`, `runner`) que aprovecha `turbo prune`, `pnpm fetch` con instalación offline y un runner minimalista sin dependencias de desarrollo. La imagen se ancla a una versión específica de Node 26 Alpine con digest SHA-256, verifica el binario de pnpm mediante suma de comprobación y ejecuta el contenedor con un usuario no root (uid 1001) sobre un sistema de archivos raíz de solo lectura, permitiendo únicamente escrituras en `.next/cache` mediante un montaje tmpfs. Incluye un HEALTHCHECK en `/api/health`, argumentos de compilación limitados a valores `NEXT_PUBLIC_*`, soporte multi-arquitectura (amd64/arm64), montajes de secretos y caché de BuildKit, etiquetas OCI y un `.dockerignore` completo. Esta habilidad es esencial para cualquier desarrollador que trabaje con Next.js, Turborepo y Docker, ya que garantiza compilaciones rápidas, imágenes pequeñas y seguras, y un despliegue predecible en Kubernetes.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Docker para Next.js

Úsalo para la imagen de la aplicación. Los detalles de standalone de Next.js están en nextjs-production (references/self-hosting.md); qué ejecuta la imagen en Kubernetes está en helm-nextjs; cómo CI la compila y la sube está en github-actions.

El apps/<app>/Dockerfile de la plantilla es la fuente de verdad; references/Dockerfile.md lo contiene completo con notas. Compila desde la raíz del repositorio: docker build -f apps/<app>/Dockerfile -t <app> .

Reglas

  1. Cinco etapas: base → pruner → deps → builder → runner.

    • base: Node + pnpm, nada más.
    • pruner: turbo prune <app> --docker sobre el repositorio completo, dejando solo la aplicación y sus dependencias del espacio de trabajo.
    • deps: copia solo out/json/ (manifiestos) y out/pnpm-lock.yaml, luego pnpm fetch + pnpm install --frozen-lockfile --offline. Esta capa se reutiliza hasta que cambie un manifiesto o el archivo lock, por lo que una edición de código fuente no reinstala.
    • builder: copia out/full/ (código fuente), pnpm exec turbo run build --filter=<app>.
    • runner: una imagen Node nueva con solo el resultado standalone. Por qué: los cambios de código fuente reconstruyen solo las últimas dos etapas, y el runner no tiene pnpm, código fuente ni dependencias de desarrollo.
  2. Fijar la imagen de Node por versión exacta y digest, coincidiendo con .node-version. node:26.12.0-alpine3.24@sha256:6f08… en un ARG NODE_IMAGE, usado tanto por base como por runner. El digest es el índice multi-arquitectura, por lo que tanto amd64 como arm64 se resuelven correctamente. Por qué Alpine en lugar de distroless: distroless Node publica solo etiquetas flotantes (latest, nonroot), por lo que la versión exacta de Node no se puede emparejar con .node-version, y no tiene wget para el HEALTHCHECK. Alpine es pequeña, fija exactamente, y sharp envía binarios musl precompilados, por lo que la optimización de imágenes funciona sin paquetes adicionales.

  3. Instalar pnpm desde el binario de la release de GitHub, verificado por SHA-256. Nunca npm i -g pnpm, nunca Corepack, nunca un curl | sh sin verificar. Un ARG PNPM_VERSION igual a packageManager, más una suma de verificación por arquitectura para pnpm-linux-{x64,arm64}-musl.tar.gz, seleccionada por TARGETARCH. Para actualizar: cambiar los tres ARGs, tomando los valores sha256 de los digests de los activos de la release (gh release view v<version> --repo pnpm/pnpm --json assets), y actualizar packageManager en el mismo commit. Plan de respaldo si el formato de la release cambia: basar las etapas pruner/deps/builder en ghcr.io/pnpm/pnpm:<version> (sin Node, añadirlo) en lugar de inventar un nuevo método de instalación.

  4. El runner copia exactamente tres cosas.

    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 no incluye static o public; sin ellos la aplicación no sirve JS ni CSS. En el monorrepositorio el servidor es apps/<app>/server.js. CMD ["node", "apps/<app>/server.js"], forma exec, para que Node reciba SIGTERM directamente.

  5. Ejecutar como uid/gid 1001 con archivos propiedad de root. Crear nextjs:nodejs (1001:1001), dejar los archivos copiados como propiedad de root (la aplicación puede leer pero no cambiar su propio código), y chown solo apps/<app>/.next/cache. USER 1001:1001, numérico, para que Kubernetes pueda verificar runAsNonRoot sin resolver nombres.

  6. La imagen debe funcionar con un sistema de archivos raíz de solo lectura. Las únicas escrituras en tiempo de ejecución van a .next/cache (optimización de imágenes, caché ISR/fetch). Kubernetes monta un emptyDir allí; localmente usar --tmpfs. Probarlo:

    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. Establecer NODE_ENV=production, HOSTNAME=0.0.0.0, PORT=3000, NEXT_TELEMETRY_DISABLED=1 en el runner. Sin HOSTNAME=0.0.0.0 el servidor standalone se vincula al hostname del contenedor y las sondas fallan.

  8. HEALTHCHECK golpea /api/health con wget de busybox. Es para docker run/Compose; Kubernetes lo ignora y usa las sondas del chart en la misma ruta.

  9. Los argumentos de compilación son solo para valores inlineados en la compilación: NEXT_PUBLIC_* y NEXT_DEPLOYMENT_ID. NEXT_PUBLIC_* se compila en el JS, por lo que debe ser un ARG + ENV en builder, y es idéntico en todos los entornos donde se ejecuta la imagen. Todo lo específico del entorno es una variable de entorno en tiempo de ejecución del chart. Para añadir uno:

    ARG NEXT_PUBLIC_APP_NAME
    ENV NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME}
    

    …además de build-args en ambos flujos de trabajo y NEXT_PUBLIC_* ya está en env de turbo.

  10. Los secretos nunca van en un ARG, ENV o COPY. Permanecerían en el historial de la imagen. Usar un montaje secreto de BuildKit, que existe solo para ese RUN:

    RUN --mount=type=secret,id=npm_token,env=NPM_TOKEN pnpm fetch   # registro privado
    

    El paso de compilación de la plantilla ya declara el secreto opcional next_server_actions_key de esta manera.

  11. Cachear el store de pnpm con un montaje de caché de BuildKit. --mount=type=cache,id=pnpm-store,target=/pnpm/store en fetch/install/dlx, con pnpm_config_store_dir=/pnpm/store. El store sobrevive entre compilaciones en el mismo builder sin terminar en una capa. En CI, el caché de capas es type=gha (ver github-actions).

  12. .dockerignore en la raíz del repositorio mantiene el contexto pequeño y los secretos fuera. Excluye node_modules, .next, .turbo, dist, coverage, .env*, .git, .github, charts. Un contexto que incluya node_modules de Windows o macOS rompe los paquetes nativos en la imagen Linux.

  13. Multi-arquitectura es linux/amd64,linux/arm64, compilado por CI. Localmente, compila tu propia arquitectura. Cada etapa es neutral en cuanto a arquitectura excepto la descarga de pnpm, que elige su suma de verificación según TARGETARCH.

  14. Etiquetas: el Dockerfile establece org.opencontainers.image.title y licenses; CI añade source, revision, version, created a través de docker/metadata-action. No codificar una etiqueta de versión.

  15. Mantener las etapas libres de hacks específicos de la aplicación. Sin apk add en el runner (la aplicación no necesita un conjunto de herramientas de shell), sin npm, sin node_modules copiados del host. Si la aplicación necesita una herramienta nativa en tiempo de ejecución, justificarlo en un comentario.

Depuración

SíntomaCausa
extends: … doesn't resolve durante la compilaciónUn archivo de nivel raíz que la aplicación necesita no estaba en el árbol podado: moverlo a un paquete del espacio de trabajo
ERR_PNPM_OUTDATED_LOCKFILE en depsLockfile no confirmado después de un cambio de manifiesto
ERR_PNPM_IGNORED_BUILDSUna nueva dependencia quiere un script de instalación: decidir en allowBuilds
La aplicación inicia, sin CSS/JS.next/static o public no copiados a apps/<app>/
La verificación de salud falla, los registros están bienHOSTNAME no es 0.0.0.0
EROFS en tiempo de ejecuciónAlgo escribe fuera de .next/cache; encontrarlo, no eliminar --read-only
NEXT_PUBLIC_X vacíoNo se pasó como argumento de compilación (se hornea en la compilación)

Verificar

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 después de ~30s
docker rm -f <app>

Fuentes

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.

Skills relacionados