# ============================================================================= # Frontend (Portal) Dockerfile - Production Grade # ============================================================================= # Uses Alpine throughout for consistency # Next.js standalone output for minimal production image # ============================================================================= ARG PNPM_VERSION=10.15.0 ARG NODE_VERSION=22 # ============================================================================= # Stage 1: Builder - Install dependencies and build Next.js # ============================================================================= FROM node:${NODE_VERSION}-alpine AS builder ARG PNPM_VERSION # Install build dependencies RUN apk add --no-cache libc6-compat \ && corepack enable \ && corepack prepare pnpm@${PNPM_VERSION} --activate WORKDIR /app # Copy workspace configuration first (better layer caching) COPY .npmrc pnpm-workspace.yaml package.json pnpm-lock.yaml ./ COPY packages/domain/package.json ./packages/domain/ COPY apps/portal/package.json ./apps/portal/ # Install all dependencies ENV HUSKY=0 RUN pnpm install --frozen-lockfile # Copy source code COPY tsconfig.json tsconfig.base.json ./ COPY packages/ ./packages/ COPY apps/portal/ ./apps/portal/ # Build domain package RUN pnpm --filter @customer-portal/domain build # Build-time environment variables (baked into Next.js client bundle) ARG NEXT_PUBLIC_API_BASE=/api ARG NEXT_PUBLIC_APP_NAME="Customer Portal" ARG NEXT_PUBLIC_APP_VERSION=1.0.0 ENV NODE_ENV=production \ NEXT_PUBLIC_API_BASE=${NEXT_PUBLIC_API_BASE} \ NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME} \ NEXT_PUBLIC_APP_VERSION=${NEXT_PUBLIC_APP_VERSION} # Build Next.js (creates standalone output) RUN pnpm --filter @customer-portal/portal build # ============================================================================= # Stage 2: Production - Minimal runtime image # ============================================================================= FROM node:${NODE_VERSION}-alpine AS production LABEL org.opencontainers.image.title="Customer Portal Frontend" \ org.opencontainers.image.description="Customer Portal Application" \ org.opencontainers.image.vendor="Customer Portal" # Runtime dependencies only RUN apk add --no-cache \ dumb-init \ wget \ curl \ libc6-compat \ && rm -rf /var/cache/apk/* # Create non-root user RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs WORKDIR /app # Copy Next.js standalone build (includes all bundled dependencies) COPY --from=builder --chown=nextjs:nodejs /app/apps/portal/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/apps/portal/.next/static ./apps/portal/.next/static COPY --from=builder --chown=nextjs:nodejs /app/apps/portal/public ./apps/portal/public RUN mkdir -p /app/logs && chown -R nextjs:nodejs /app USER nextjs EXPOSE 3000 ENV NODE_ENV=production \ NEXT_TELEMETRY_DISABLED=1 \ PORT=3000 \ HOSTNAME="0.0.0.0" HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1 ENTRYPOINT ["dumb-init", "--"] CMD ["node", "apps/portal/server.js"]