# ============================================================================= # Backend (BFF) Dockerfile - Production Grade # ============================================================================= # Uses Alpine throughout for consistent native module builds # Uses pnpm prune instead of pnpm deploy to preserve Prisma client # ============================================================================= ARG PNPM_VERSION=10.15.0 ARG NODE_VERSION=22 # ============================================================================= # Stage 1: Builder - Install, build, and prune for production # ============================================================================= FROM node:${NODE_VERSION}-alpine AS builder ARG PNPM_VERSION # Install build dependencies for native modules (bcrypt, prisma) RUN apk add --no-cache \ python3 make g++ pkgconfig openssl-dev 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 packages/logging/package.json ./packages/logging/ COPY apps/bff/package.json ./apps/bff/ # 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/bff/ ./apps/bff/ # Build workspace packages RUN pnpm --filter @customer-portal/domain build && \ pnpm --filter @customer-portal/logging build # Generate Prisma client (for Alpine/musl) RUN cd apps/bff && pnpm exec prisma generate # Build BFF RUN pnpm --filter @customer-portal/bff build # Prune dev dependencies IN PLACE - this keeps .prisma/client intact RUN pnpm prune --prod # Remove unnecessary files to reduce image size RUN rm -rf /app/packages/*/src /app/apps/bff/src \ /app/packages/*/*.ts /app/apps/bff/*.ts \ /app/**/*.map /app/**/*.tsbuildinfo \ /root/.local/share/pnpm/store # ============================================================================= # Stage 2: Production - Minimal runtime image # ============================================================================= FROM node:${NODE_VERSION}-alpine AS production LABEL org.opencontainers.image.title="Customer Portal BFF" \ org.opencontainers.image.description="NestJS Backend-for-Frontend API" \ org.opencontainers.image.vendor="Customer Portal" # Runtime dependencies only RUN apk add --no-cache \ dumb-init wget openssl netcat-openbsd libc6-compat \ && rm -rf /var/cache/apk/* # Create non-root user RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nestjs WORKDIR /app # Copy pruned node_modules (includes .prisma/client) COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules # Copy workspace package outputs COPY --from=builder --chown=nestjs:nodejs /app/packages/domain/dist ./packages/domain/dist COPY --from=builder --chown=nestjs:nodejs /app/packages/domain/package.json ./packages/domain/package.json COPY --from=builder --chown=nestjs:nodejs /app/packages/logging/dist ./packages/logging/dist COPY --from=builder --chown=nestjs:nodejs /app/packages/logging/package.json ./packages/logging/package.json # Copy BFF COPY --from=builder --chown=nestjs:nodejs /app/apps/bff/dist ./apps/bff/dist COPY --from=builder --chown=nestjs:nodejs /app/apps/bff/prisma ./apps/bff/prisma COPY --from=builder --chown=nestjs:nodejs /app/apps/bff/package.json ./apps/bff/package.json # Copy entrypoint COPY --chown=nestjs:nodejs apps/bff/scripts/docker-entrypoint.sh /app/docker-entrypoint.sh RUN chmod +x /app/docker-entrypoint.sh && \ mkdir -p /app/secrets /app/logs && \ chown nestjs:nodejs /app/secrets /app/logs USER nestjs EXPOSE 4000 ENV NODE_ENV=production PORT=4000 WORKDIR /app/apps/bff HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:4000/health || exit 1 ENTRYPOINT ["dumb-init", "--", "/app/docker-entrypoint.sh"] CMD ["node", "dist/main.js"]