FROM node:22-alpine AS builder # Install pnpm and build deps for native modules (e.g., bcrypt) RUN npm install -g pnpm typescript \ && apk add --no-cache python3 make g++ libc6-compat ENV npm_config_build_from_source=1 \ npm_config_python=python3 RUN pnpm config set enable-pre-post-scripts true WORKDIR /app # Copy full sources and build both client and server COPY . . RUN pnpm install --frozen-lockfile --ignore-scripts=false --enable-pre-post-scripts \ && pnpm run build FROM node:22-alpine AS production # Install pnpm, runtime tools and build deps for native modules present in prod deps RUN npm install -g pnpm \ && apk add --no-cache su-exec curl python3 make g++ libc6-compat ENV npm_config_build_from_source=1 \ npm_config_python=python3 RUN pnpm config set enable-pre-post-scripts true # Set working directory WORKDIR /app # Copy package files COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ # Install production dependencies only RUN pnpm install --frozen-lockfile --prod --ignore-scripts=false --enable-pre-post-scripts \ && echo "[production] Rebuilding bcrypt for Alpine Linux..." \ && pnpm rebuild bcrypt --build-from-source \ && echo "[production] Verifying bcrypt installation..." \ && node -e "require('bcrypt')" \ && echo "[production] Removing build toolchain to reduce image size..." \ && apk del python3 make g++ # Copy built artifacts from builder COPY --from=builder /app/dist ./dist COPY --from=builder /app/server-dist ./server-dist # public wird aus dem Kontext kopiert COPY public ./public # Copy necessary runtime files COPY start.sh ./start.sh # Create non-root user for security RUN addgroup -g 1001 -S nodejs RUN adduser -S nextjs -u 1001 # Make start script executable RUN chmod +x /app/start.sh # Change ownership of the app directory RUN chown -R nextjs:nodejs /app || true # Expose port EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" || exit 1 # Start the application with startup script CMD ["/app/start.sh"] # Prebuilt runtime stage (used locally): copies prebuilt dist and server-dist from context FROM node:22-alpine AS production-prebuilt RUN npm install -g pnpm \ && apk add --no-cache su-exec curl python3 make g++ libc6-compat ENV npm_config_build_from_source=1 \ npm_config_python=python3 RUN pnpm config set enable-pre-post-scripts true WORKDIR /app COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN pnpm install --frozen-lockfile --prod --ignore-scripts=false --enable-pre-post-scripts \ && echo "[production-prebuilt] Rebuilding bcrypt for Alpine Linux..." \ && pnpm rebuild bcrypt --build-from-source \ && echo "[production-prebuilt] Verifying bcrypt installation..." \ && node -e "require('bcrypt')" # Copy prebuilt artifacts from repository COPY dist ./dist COPY server-dist ./server-dist COPY public ./public COPY start.sh ./start.sh RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001 \ && chmod +x /app/start.sh \ && chown -R nextjs:nodejs /app || true EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" || exit 1 CMD ["/app/start.sh"]