# syntax=docker/dockerfile:1 # ============================================================================= # BFF (NestJS) Dockerfile # ============================================================================= # Multi-stage build with BuildKit cache mounts for fast rebuilds # ============================================================================= ARG NODE_VERSION=22 ARG PNPM_VERSION=10.15.0 ARG PRISMA_VERSION=6.16.0 # ============================================================================= # Stage 1: Builder # ============================================================================= FROM node:${NODE_VERSION}-alpine AS builder ARG PNPM_VERSION ARG PRISMA_VERSION RUN apk add --no-cache python3 make g++ openssl libc6-compat \ && corepack enable \ && corepack prepare pnpm@${PNPM_VERSION} --activate WORKDIR /app # Copy manifests first for dependency caching COPY .npmrc pnpm-workspace.yaml package.json pnpm-lock.yaml ./ COPY packages/domain/package.json ./packages/domain/ COPY apps/bff/package.json ./apps/bff/ # Install dependencies with cache mount ENV HUSKY=0 RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \ pnpm install --frozen-lockfile # Copy source files COPY tsconfig.json tsconfig.base.json ./ COPY packages/domain/ ./packages/domain/ COPY apps/bff/ ./apps/bff/ # Build domain → generate Prisma (for type-checking) → build BFF RUN pnpm --filter @customer-portal/domain build \ && cd apps/bff && pnpm exec prisma generate && cd ../.. \ && pnpm --filter @customer-portal/bff build # Create production deployment bundle # pnpm deploy creates a standalone package with production dependencies RUN pnpm deploy --filter @customer-portal/bff --prod /app/deploy \ && cp -r apps/bff/dist deploy/dist \ && cp -r apps/bff/prisma deploy/prisma \ && cp -r packages/domain/dist deploy/node_modules/@customer-portal/domain/dist # ============================================================================= # Stage 2: Production # ============================================================================= FROM node:${NODE_VERSION}-alpine AS production ARG PRISMA_VERSION LABEL org.opencontainers.image.title="Customer Portal BFF" \ org.opencontainers.image.description="NestJS Backend-for-Frontend API" RUN apk add --no-cache dumb-init libc6-compat netcat-openbsd wget \ && addgroup --system --gid 1001 nodejs \ && adduser --system --uid 1001 nestjs WORKDIR /app # Copy deploy bundle COPY --from=builder --chown=nestjs:nodejs /app/deploy ./ # Regenerate Prisma client in the final container layout (/app) so the embedded # schema path matches /app/prisma/schema.prisma. Then ensure the runtime user # owns the generated client assets. ENV PRISMA_SCHEMA_PATH=/app/prisma/schema.prisma RUN rm -rf node_modules/.prisma \ && npx prisma@${PRISMA_VERSION} generate --schema=${PRISMA_SCHEMA_PATH} \ # Ensure legacy path exists for any stale schemaPath references && mkdir -p /app/apps/bff/prisma \ && cp /app/prisma/schema.prisma /app/apps/bff/prisma/schema.prisma \ # Patch any generated files that may still embed the monorepo schema path && find node_modules -type f -path "*/.prisma/client/*.js" -exec sed -i 's|apps/bff/prisma/schema\\.prisma|/app/prisma/schema.prisma|g' {} + \ # Fix ownership for runtime user && find node_modules -type d -name ".prisma" -exec chown -R nestjs:nodejs {} + \ && chown -R nestjs:nodejs /app/apps/bff/prisma \ && npm cache clean --force \ && rm -rf /root/.npm/_npx /root/.npm/_cacache /tmp/* # Copy and setup entrypoint COPY --chown=nestjs:nodejs apps/bff/scripts/docker-entrypoint.sh ./docker-entrypoint.sh RUN chmod +x docker-entrypoint.sh \ && mkdir -p secrets logs \ && chown nestjs:nodejs secrets logs USER nestjs EXPOSE 4000 ENV NODE_ENV=production \ PORT=4000 \ PRISMA_VERSION=${PRISMA_VERSION} \ PRISMA_SCHEMA_PATH=/app/prisma/schema.prisma HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD node -e "fetch('http://localhost:4000/health').then(r=>r.ok||process.exit(1)).catch(()=>process.exit(1))" ENTRYPOINT ["dumb-init", "--", "./docker-entrypoint.sh"] CMD ["node", "dist/main.js"]