Ctrl K
ComunidadGuías…

next-app/Dockerfile

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

Archivo original ↗Preguntar a KiBot
# syntax=docker/dockerfile:1.7

# next-app multi-stage Docker build.
#
#   builder  : install dev + prod deps, run `next build`. Bun resolves the
#              workspace standalone (next-app is NOT a root workspace
#              member since P4.1.0 to avoid Turbopack hoisting issues).
#   runner   : copy the standalone bundle + .next/static + public assets,
#              run with raw `node server.js`. Bun is only used for the
#              build phase; the runtime is plain Node 22-alpine because
#              standalone server.js has no Bun-specific runtime needs and
#              keeps the image small.
#
# Build:   docker compose build next-app
# Image:   ~120-150MB final layer (vs ~600MB without standalone)

FROM oven/bun:1.3.14-alpine AS deps
WORKDIR /app
COPY next-app/package.json next-app/bun.lock ./
# --frozen-lockfile pins to bun.lock exactly; fails the build if the
# lockfile is stale instead of silently resolving to newer versions.
RUN bun install --frozen-lockfile

FROM oven/bun:1.3.14-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY next-app/ ./
# Disable Next telemetry at build time. Production opt-out lives in the
# runner stage too.
ENV NEXT_TELEMETRY_DISABLED=1
# Sentry build-time variables. NEXT_PUBLIC_SENTRY_DSN MUST be available
# at build because Next inlines NEXT_PUBLIC_* into the client bundle —
# runtime env is too late, the bundle is already compiled. Default ""
# means a build without these args still succeeds (Sentry SDK no-ops on
# falsy DSN). SENTRY_AUTH_TOKEN drives source-map upload via
# next.config.ts's `sourcemaps.disable: !process.env.SENTRY_AUTH_TOKEN`
# gate — without it, the upload step skips and the build still passes.
#
# SENTRY_ORG / SENTRY_PROJECT are the other half of that gate and were the
# reason a token alone would not have worked: next.config.ts does not name
# an org or project, so the plugin resolves both from the environment. With
# the token set and these two absent, the upload has a credential and no
# destination — it fails at build rather than skipping, which is the loud
# half of a footgun whose quiet half (no token at all) had been hiding it.
ARG NEXT_PUBLIC_SENTRY_DSN=
ARG SENTRY_AUTH_TOKEN=
ARG SENTRY_ORG=
ARG SENTRY_PROJECT=
ENV NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN
ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN
ENV SENTRY_ORG=$SENTRY_ORG
ENV SENTRY_PROJECT=$SENTRY_PROJECT

# The AniList image mirror switch (src/lib/images/mirror.ts): the URL prefix
# AniList image URLs are rewritten to, https://animegoclub.com/img/anilist/ in
# production. NEXT_PUBLIC_* for the same reason as the DSN above -- Next
# inlines it into both bundles at build time, so only a build arg reaches it.
# Empty (the default) keeps every image on AniList's own URLs, which is what a
# build without the nginx mirror in front of it needs.
ARG NEXT_PUBLIC_IMAGE_MIRROR_BASE=
ENV NEXT_PUBLIC_IMAGE_MIRROR_BASE=$NEXT_PUBLIC_IMAGE_MIRROR_BASE

# The commit this image was built from. next.config.ts turns it into
# NEXT_PUBLIC_BUILD_ID, which is how a tab opened before a deploy learns it
# is stale. Empty is fine and falls back to a timestamp -- the only thing
# lost is that rebuilding the same commit then reads as a new deployment.
ARG GIT_SHA=
ENV GIT_SHA=$GIT_SHA
# P6.6 pin: --webpack. Next 16's turbopack default deadlocks on the
# Library + Player port surface (23k LOC + 4.5 MB jassub WASM + worker
# URLs in usePlaybackSession). Webpack mode compiles in ~15 s. Drop
# the flag when turbopack stabilises asset-handling for those paths.
RUN bun --bun next build --webpack

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME=0.0.0.0

# Non-root user for runtime. UID 1001 matches the node:alpine convention
# used by Next.js official samples.
RUN addgroup --system --gid 1001 nodejs \
 && adduser  --system --uid 1001 nextjs

# `output: "standalone"` produces .next/standalone with a minimal
# server.js + only the deps server-side actually imports. Static assets
# and the public dir must be copied alongside since standalone does NOT
# bundle them.
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

# .next/cache/images is a named volume (docker-compose.yml). A new named volume
# takes its owner from the image's directory at the mount point, so the
# directory has to exist here, owned by the runtime user; otherwise the volume
# is created root-owned and the image optimizer cannot write its cache.
RUN mkdir -p .next/cache/images && chown -R nextjs:nodejs .next/cache

USER nextjs
EXPOSE 3000

# Use 127.0.0.1 instead of localhost: localhost resolves to ::1 first on
# node:alpine, but next 16 standalone server only listens on the IPv4
# wildcard (HOSTNAME=0.0.0.0). wget then connects to v6, fails, marks
# the container unhealthy even though every external request works.
HEALTHCHECK --interval=15s --timeout=3s --start-period=10s --retries=3 \
  CMD wget -qO- http://127.0.0.1:3000/api/healthz >/dev/null 2>&1 || exit 1

CMD ["node", "server.js"]