# 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 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 --from=deps /app/node_modules ./node_modules COPY full/ . ENV 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 # standalone output mirrors the monorepo tree, so server.js lands at /app/server.js # Static files and public/ must be copied separately — they are not in standalone/ 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", "server.js"]