# Payload CMS Dockerfile - Minimal ~200MB build # Uses node-linker=hoisted to avoid pnpm symlinks in standalone output # Stage 1: Build FROM node:22-alpine AS builder WORKDIR /app # Install pnpm RUN corepack enable && corepack prepare pnpm@10.17.0 --activate # Copy workspace files COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./ COPY apps/backend/package.json ./apps/backend/ COPY packages/shared/package.json ./packages/shared/ # Install dependencies with hoisted node_modules (no symlinks) # This makes standalone output work correctly RUN pnpm install --frozen-lockfile --config.node-linker=hoisted # Copy source files COPY apps/backend ./apps/backend COPY packages/shared ./packages/shared # Build Next.js with standalone output WORKDIR /app/apps/backend RUN pnpm run build # Stage 2: Production (minimal image) FROM node:22-alpine AS runner # Monorepo standalone structure: server.js is at apps/backend/server.js WORKDIR /app/apps/backend ENV NODE_ENV=production ENV PORT=3000 # Combine user creation into single layer RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs # Copy standalone (preserves monorepo structure with node_modules at root) COPY --from=builder --chown=nextjs:nodejs /app/apps/backend/.next/standalone ./../../ # Copy static assets (not included in standalone by default) COPY --from=builder --chown=nextjs:nodejs /app/apps/backend/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/apps/backend/public ./public USER nextjs EXPOSE 3000 # Health check for container orchestration HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD node -e "require('http').get('http://localhost:3000/', (r) => process.exit(r.statusCode < 500 ? 0 : 1)).on('error', () => process.exit(1))" || exit 1 # Monorepo path: server.js is at /app/apps/backend/server.js CMD ["node", "server.js"]