# Build context: ./out (turbo prune storefront --docker) # out/json/ — package.json files only → used by deps stage for layer caching # out/full/ — full pruned monorepo → used by builder stage for source # out/package-lock.json # ── Stage 1: deps ──────────────────────────────────────────────────────────── # Install ALL dependencies (dev + prod) using only the package.json tree. # This layer is shared with the builder stage and only rebuilds when # a package.json or the lock file changes — not when source code changes. FROM node:20-alpine AS deps RUN apk add --no-cache libc6-compat WORKDIR /app # Upgrade npm to match the project's packageManager (npm@11). The package-lock.json # was generated with npm 11 — npm 10 (bundled with node:20) can't fully parse it, # causing turbo prune to generate an incomplete pruned lockfile and npm ci to miss # packages like @heroui/react. RUN npm install -g npm@11 --quiet COPY json/ . COPY package-lock.json . RUN npm ci # ── Stage 2: builder ───────────────────────────────────────────────────────── # Full monorepo source + build artifact. # next build produces .next/standalone/ because output: "standalone" is set # in next.config.js — that's what makes the runner stage small. FROM node:20-alpine AS builder WORKDIR /app # Copy everything from the deps stage — not just /app/node_modules. # @heroui/react cannot be hoisted to the root by npm and is installed at # apps/storefront/node_modules/ instead. Copying only the root node_modules # would leave it missing. Copying all of /app/ brings both root and # workspace-level node_modules, then full/ layers the source on top. COPY --from=deps /app/ ./ COPY full/ . # NEXT_PUBLIC_* vars are baked into the client bundle at build time by Next.js. # They must be present here (not just at runtime) or SSG/prerender fails. # Passed via --build-arg in CI. Note: Gitea secrets use a STAGING_/PROD_ prefix # which is stripped by the workflow before being forwarded here as build args. ARG NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ARG NEXT_PUBLIC_CONVEX_URL ARG NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ENV NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY \ NEXT_PUBLIC_CONVEX_URL=$NEXT_PUBLIC_CONVEX_URL \ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=$NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY \ NEXT_TELEMETRY_DISABLED=1 RUN npx turbo build --filter=storefront # ── Stage 3: runner ────────────────────────────────────────────────────────── # Minimal runtime image — only the standalone bundle, static assets, and public dir. # No source code, no dev dependencies, no build tools. FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production \ NEXT_TELEMETRY_DISABLED=1 \ HOSTNAME=0.0.0.0 \ PORT=3000 # Non-root user for security RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001 # outputFileTracingRoot is set to the repo root, so the standalone directory mirrors # the full monorepo tree. server.js lands at apps/storefront/server.js inside # standalone/, not at the root. Static files and public/ must be copied separately. COPY --from=builder --chown=nextjs:nodejs /app/apps/storefront/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/apps/storefront/.next/static ./apps/storefront/.next/static COPY --from=builder --chown=nextjs:nodejs /app/apps/storefront/public ./apps/storefront/public USER nextjs EXPOSE 3000 CMD ["node", "apps/storefront/server.js"]