# syntax=docker/dockerfile:1.7 ARG NODE_IMAGE=node:24.18.0-bookworm-slim # ============================================================ # 1. Dependencias reproducibles # ============================================================ FROM ${NODE_IMAGE} AS deps WORKDIR /app ENV NEXT_TELEMETRY_DISABLED=1 \ NPM_CONFIG_REGISTRY=https://registry.npmjs.org/ \ NPM_CONFIG_REPLACE_REGISTRY_HOST=always \ NPM_CONFIG_AUDIT=false \ NPM_CONFIG_FUND=false \ NPM_CONFIG_FETCH_RETRIES=2 \ NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=5000 \ NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=30000 \ NPM_CONFIG_FETCH_TIMEOUT=120000 \ NPM_CONFIG_MAXSOCKETS=8 \ NPM_CONFIG_LOGLEVEL=info COPY package.json package-lock.json .npmrc ./ # Evita que un package-lock generado en otro entorno apunte a un # registry privado o inaccesible para el runner de Gitea. RUN node <<'NODE' const fs = require("node:fs"); const file = "package-lock.json"; const text = fs.readFileSync(file, "utf8"); const forbiddenHosts = [ "internal.api.openai.org", "packages.applied-caas-gateway1", ]; const detected = forbiddenHosts.filter((host) => text.includes(host)); if (detected.length > 0) { console.error( `ERROR: package-lock.json contiene registry no permitido: ${detected.join(", ")}` ); process.exit(1); } const lock = JSON.parse(text); console.log( `Lockfile válido: ${lock.name} · lockfileVersion=${lock.lockfileVersion}` ); NODE RUN node --version \ && npm --version \ && npm config get registry \ && npm config get replace-registry-host \ && npm ping # --kill-after garantiza que un npm colgado termine después de SIGTERM. RUN --mount=type=cache,target=/root/.npm,sharing=private \ timeout \ --signal=TERM \ --kill-after=30s \ 900s \ npm ci \ --no-audit \ --no-fund \ --prefer-online \ --loglevel=info \ --timing # ============================================================ # 2. Compilación de Next.js # ============================================================ FROM ${NODE_IMAGE} AS builder WORKDIR /app ENV NODE_ENV=production \ NEXT_TELEMETRY_DISABLED=1 \ NODE_OPTIONS=--max-old-space-size=2048 COPY --from=deps /app/node_modules ./node_modules COPY . . RUN --mount=type=cache,target=/app/.next/cache,sharing=private \ npm run build RUN test -f /app/.next/standalone/server.js # ============================================================ # 3. Runtime de producción # ============================================================ FROM ${NODE_IMAGE} AS runner WORKDIR /app ENV DEBIAN_FRONTEND=noninteractive \ NODE_ENV=production \ NEXT_TELEMETRY_DISABLED=1 \ HOSTNAME=0.0.0.0 \ PORT=80 RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && groupadd --gid 1001 nodejs \ && useradd \ --uid 1001 \ --gid 1001 \ --create-home \ --shell /usr/sbin/nologin \ nextjs \ && setcap 'cap_net_bind_service=+ep' \ /usr/local/bin/node COPY --from=builder \ --chown=nextjs:nodejs \ /app/public \ ./public COPY --from=builder \ --chown=nextjs:nodejs \ /app/.next/standalone \ ./ COPY --from=builder \ --chown=nextjs:nodejs \ /app/.next/static \ ./.next/static USER nextjs EXPOSE 80 HEALTHCHECK \ --interval=30s \ --timeout=8s \ --start-period=30s \ --retries=3 \ CMD ["node", "-e", "fetch('http://127.0.0.1/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"] CMD ["node", "server.js"]