# Production Next.js image for the community landing/marketing site. # # Build context is the `community/` root (see ../../docker-compose.yml), not this # directory, so every COPY below is rooted at `apps/landing/...`. # # apps/landing/next.config.ts only enables `output: "standalone"` when the # NEXT_OUTPUT build-time env var is exactly "standalone" (verified against # next.config.ts, which also sets security headers unconditionally). Setting # NEXT_OUTPUT below is what makes `next build` emit .next/standalone. # # NEXT_PUBLIC_* vars are baked into the client bundle at build time (they run # in the end user's browser, not inside this container), so they must be the # host-facing URLs docker-compose publishes each service on (localhost:), # not the internal Docker service names -- matches the original app's build-time # env handling and infra/deploy/landing/Dockerfile. FROM node:20-alpine AS deps WORKDIR /app COPY apps/landing/package.json apps/landing/package-lock.json ./ RUN npm ci FROM node:20-alpine AS builder WORKDIR /app ENV NEXT_OUTPUT=standalone # Verified via next.config.ts + grep of process.env.NEXT_PUBLIC_* under # apps/landing/{app,components,lib} -- landing only reads these four. ARG NEXT_PUBLIC_API_URL=http://localhost:4000 ARG NEXT_PUBLIC_LANDING_URL=http://localhost:3001 ARG NEXT_PUBLIC_DASHBOARD_URL=http://localhost:53000 ARG NEXT_PUBLIC_ADMIN_URL=http://localhost:3002 ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \ NEXT_PUBLIC_LANDING_URL=$NEXT_PUBLIC_LANDING_URL \ NEXT_PUBLIC_DASHBOARD_URL=$NEXT_PUBLIC_DASHBOARD_URL \ NEXT_PUBLIC_ADMIN_URL=$NEXT_PUBLIC_ADMIN_URL COPY --from=deps /app/node_modules ./node_modules COPY apps/landing/. . # Verified: apps/landing/package.json "build" == "next build". RUN npm run build FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production \ HOSTNAME=0.0.0.0 \ PORT=3001 RUN addgroup --system --gid 1001 nodejs \ && adduser --system --uid 1001 nextjs 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 3001 # Probe a static asset, NOT `/`. The root of this app is a server-rendered # page; probing it every 15 s made the container render its own homepage # 5,760 times a day, each render fanning out to the API and from there to # Postgres (for landing: 3 catalog calls + stats per render). Measured on # 2026-09-22 as the bulk of a steady ~10 GB/day of Supabase egress. The # favicon is served straight from the standalone build with no data fetch, # so it still proves the Node server is up and answering HTTP, which is all # a liveness probe should prove. 60 s is plenty for a restart-on-failure # policy; a 15 s cadence was never load-bearing for anything. HEALTHCHECK --interval=60s --timeout=5s --start-period=15s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT || 3001) + '/favicon.ico').then(r => process.exit(r.status < 500 ? 0 : 1)).catch(() => process.exit(1))" # Verified: .next/standalone always emits server.js as the entrypoint. CMD ["node", "server.js"]