Ctrl K
ComunidadGuías…

Dockerfile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# syntax=docker/dockerfile:1.7
# =============================================================================
# Dockerfile - multi-stage build for PowerDNS-AuthAdmin
#
# Stages:
#   builder  - full deps install, Next.js production build, AND pre-bundle
#              of the three boot-time TS scripts into self-contained ESM
#              files. Three artefacts feed the runner:
#                1. `.next/standalone` - Next's traced server bundle
#                   (incl. the runtime node_modules subset Next computed).
#                2. `.next/static` + `public` - static assets.
#                3. `boot/{migrate,seed,provision}.js` - esbuild-bundled
#                   ESM versions of the .ts scripts, every `lib/*` import
#                   inlined.
#
#   fs-prep - bookworm-slim layout-and-strip stage. Distroless has no
#             shell, no apt, no `mkdir`, no `strip`. So we assemble the
#             final filesystem here (chown, strip native .node binaries
#             to drop debug symbols, prepare /data) and COPY the prepared
#             tree as a single layer into the distroless runner.
#
#   runner  - `gcr.io/distroless/nodejs24-debian12:nonroot`. Non-root by
#             default (uid 65532 `nonroot`). NO shell, NO package manager,
#             NO source tree, NO tsx, NO separate prod-deps node_modules.
#             Boot bundles run with `node` directly. Externals (native
#             bindings, pg, pino transports) resolve from the standalone
#             bundle's already-traced node_modules.
#
# Critical: builder + fs-prep use Debian/glibc (NOT alpine/musl). napi-rs
# native bindings (@node-rs/argon2, better-sqlite3) resolve to different
# prebuilt binaries by libc; a builder on musl would install musl
# binaries that can't be dlopen()'d by the glibc runner. Both bases
# share the same glibc to keep the bindings portable.
#
# Trade-off (vs. bookworm-slim runner): no shell at runtime - incident
# triage via `docker exec <id> sh` is unavailable. If you need it, build
# a separate `:debug` tag against bookworm-slim using the same builder.
# =============================================================================

# --- Stage 1: builder --------------------------------------------------------
FROM node:24-bookworm-slim@sha256:242549cd46785b480c832479a730f4f2a20865d61ea2e404fdb2a5c3d3b73ecf AS builder

RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 build-essential ca-certificates \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY package.json package-lock.json ./

# `npm ci` only - it installs strictly from the committed lockfile, so the
# build is reproducible and every dependency version is hash-pinned via
# package-lock.json. No `npm install` fallback: the lockfile is committed,
# and an image that silently un-pinned-installs on a missing lock would be
# a supply-chain regression (OpenSSF Scorecard "Pinned-Dependencies").
RUN --mount=type=cache,target=/root/.npm \
    npm ci --include=optional

COPY . .

ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production

# Two outputs from the same source tree:
#   • npm run build       → .next/standalone (Next.js production server)
#   • npm run build:boot  → boot/{migrate,seed,provision}.js (esbuild)
RUN npm run build && npm run build:boot


# --- Stage 2: fs-prep --------------------------------------------------------
# Distroless has no shell to run mkdir / chown / strip. We assemble the final
# tree here under bookworm-slim where we DO have those tools.
FROM node:24-bookworm-slim@sha256:242549cd46785b480c832479a730f4f2a20865d61ea2e404fdb2a5c3d3b73ecf AS fs-prep

WORKDIR /stage

COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
COPY --from=builder /app/boot ./boot
COPY --from=builder /app/drizzle ./drizzle
COPY --from=builder /app/drizzle-sqlite ./drizzle-sqlite
COPY --from=builder /app/docker/entrypoint.mjs ./entrypoint.mjs

# Strip debug symbols from every .node native binding we ship
# (better-sqlite3, @node-rs/argon2). Sub-MB each but free; the stripped
# binaries dlopen identically. binutils is purged before COPY so none of
# it lands in the distroless runner.
RUN apt-get update && apt-get install -y --no-install-recommends binutils \
    && find . -name '*.node' -exec strip --strip-unneeded {} \; \
    && apt-get purge -y binutils \
    && apt-get autoremove -y \
    && rm -rf /var/lib/apt/lists/*

# Distroless `nonroot`: uid 65532, gid 65532.
RUN chown -R 65532:65532 /stage && \
    mkdir -p /data-stage && chown -R 65532:65532 /data-stage


# --- Stage 3: runner (distroless) -------------------------------------------
# Digest-pinned like the builder/fs-prep stages above (supply-chain
# reproducibility; OpenSSF Scorecard "Pinned-Dependencies"). This is the
# multi-arch index digest for :nonroot - bump it alongside the base-image
# refresh that updates the node:24-bookworm-slim digests.
FROM gcr.io/distroless/nodejs24-debian12:nonroot@sha256:14d42e2511532589a7c7e01a753667a74fcc96266e137e8125006b87b0c32d0a AS runner

ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
ENV NEXT_TELEMETRY_DISABLED=1

# Distroless puts `node` at /nodejs/bin/node and runs it as the image's
# ENTRYPOINT, but does NOT add /nodejs/bin to $PATH. Compose health-
# checks of the form `CMD ["node", ...]` bypass ENTRYPOINT and exec
# `node` directly from PATH - they need it visible there.
ENV PATH=/nodejs/bin:$PATH

# Build provenance surfaced in the UI (sidebar version chip + GitHub/Docs
# links, see lib/app-meta.ts). The CI docker job sets GIT_SHA to the
# commit and APP_RELEASE=true only for vX.Y.Z tag builds. Both default
# empty/false for a plain `docker build`, which makes the app fall back
# to local-dev labels and main-branch links.
ARG GIT_SHA=""
ARG APP_RELEASE="false"
ENV APP_GIT_SHA=$GIT_SHA
ENV APP_RELEASE=$APP_RELEASE

WORKDIR /app

COPY --from=fs-prep --chown=nonroot:nonroot /stage/ /app/
COPY --from=fs-prep --chown=nonroot:nonroot /data-stage /data

USER nonroot

EXPOSE 3000

# Image-level liveness probe, same shape as the compose services' healthcheck
# (distroless has no curl/wget; `node` is on PATH per the ENV above). Compose
# `healthcheck:` blocks and Kubernetes liveness/readiness probes override or
# ignore this respectively, so it only governs plain `docker run` deployments,
# where it is the only thing that lets `docker ps` show the app as unhealthy.
# `/healthz` is the liveness endpoint; `/readyz` (DB + migrations) is left to
# orchestrators that can act on readiness separately.
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
  CMD ["node", "-e", "fetch('http://127.0.0.1:3000/healthz').then(r=>r.ok?process.exit(0):process.exit(1)).catch(()=>process.exit(1))"]

# Distroless/nodejs ships with `node` as the entrypoint; the CMD is the
# script path. Same effect as `node entrypoint.mjs` on bookworm-slim.
CMD ["entrypoint.mjs"]