# ── Stage 1: install dependencies ──────────────────────────────────────────── FROM mirror.soroushasadi.com/node:20-alpine AS deps # NOTE: do NOT `apk add libc6-compat` here — the deps stage only runs `npm ci` # (which doesn't need it) and the build/runtime stages omit it anyway. Pulling it # reaches Alpine's public CDN (dl-cdn.alpinelinux.org), which is unreachable from # the CI server (only the Nexus mirror is) and fails the whole build. WORKDIR /app COPY package.json package-lock.json* ./ # npm installs through the self-hosted Nexus mirror (override with --build-arg # NPM_REGISTRY=... for a different mirror). The proxy intermittently returns 500s # / corrupted tarballs while it back-fills from upstream, so retry the whole # install a few times — each pass re-requests only what's still missing. ARG NPM_REGISTRY=https://mirror.soroushasadi.com/repository/npm-group/ RUN for i in 1 2 3 4 5; do \ npm ci --registry "${NPM_REGISTRY}" \ --fetch-retries=5 --fetch-retry-factor=2 \ --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && exit 0; \ echo "npm ci attempt $i failed; retrying in 10s..."; sleep 10; \ done; \ echo "npm ci failed after 5 attempts" && exit 1 # ── Stage 2: build ─────────────────────────────────────────────────────────── FROM mirror.soroushasadi.com/node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # NEXT_PUBLIC_* vars are embedded at build time — pass them as build args. # Server-side secrets (STRIPE_SECRET_KEY, SUPABASE_SERVICE_ROLE_KEY, etc.) # are injected at runtime via env / docker-compose and never baked into the image. ARG NEXT_PUBLIC_SUPABASE_URL ARG NEXT_PUBLIC_SUPABASE_ANON_KEY ARG NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ARG NEXT_PUBLIC_SITE_URL=http://localhost:3000 # V2: browser-facing gateway base (host-exposed port) + tenant for Identity auth ARG NEXT_PUBLIC_API_URL=http://localhost:8088/v1 ARG NEXT_PUBLIC_TENANT_SLUG=flatrender # Browser-reachable MinIO base for public (user-uploads) object URLs. ARG NEXT_PUBLIC_MINIO_URL=http://localhost:9000 ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=$NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL ENV NEXT_PUBLIC_TENANT_SLUG=$NEXT_PUBLIC_TENANT_SLUG ENV NEXT_PUBLIC_MINIO_URL=$NEXT_PUBLIC_MINIO_URL ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production RUN npm run build # ── Stage 3: production runner ──────────────────────────────────────────────── FROM mirror.soroushasadi.com/node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 # Create a non-root user (security best practice) RUN addgroup --system --gid 1001 nodejs \ && adduser --system --uid 1001 nextjs # Copy public assets COPY --from=builder /app/public ./public # standalone output: server.js + chunk bundles (no full node_modules) COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # Prepare prerender cache dir with correct ownership RUN mkdir -p .next && chown nextjs:nodejs .next USER nextjs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME=0.0.0.0 # Next.js standalone entry point CMD ["node", "server.js"]