# PIG — production image. # # Multi-stage so the runtime image carries no build toolchain and no source # maps. The front end is built into the API's static directory and served from # the same origin, which matters for more than tidiness: auth sessions are # per-origin, so splitting the app across two hostnames turns sign-in into a # redirect loop that looks like a broken deployment. FROM node:22-alpine AS build WORKDIR /app # Manifests first, so a dependency install is cached across source-only edits. COPY package.json package-lock.json* ./ COPY packages/core/package.json packages/core/ COPY packages/db/package.json packages/db/ COPY packages/prime/package.json packages/prime/ COPY apps/api/package.json apps/api/ COPY apps/web/package.json apps/web/ COPY apps/mcp/package.json apps/mcp/ COPY apps/piggy/package.json apps/piggy/ RUN npm install --no-audit --no-fund COPY . . # Typecheck as a build gate. A deploy that does not compile should fail here, # loudly, rather than at runtime in front of a user. RUN npx tsc --noEmit -p packages/core/tsconfig.json \ && npx tsc --noEmit -p packages/db/tsconfig.json \ && npx tsc --noEmit -p packages/prime/tsconfig.json \ && npx tsc --noEmit -p apps/api/tsconfig.json \ && npx tsc --noEmit -p apps/web/tsconfig.json \ && npx tsc --noEmit -p apps/mcp/tsconfig.json \ && npx tsc --noEmit -p apps/piggy/tsconfig.json RUN npm run build -w @pig/web # ---------------------------------------------------------------- runtime FROM node:22-alpine AS runtime WORKDIR /app ENV NODE_ENV=production # Reinstall without dev dependencies. tsx is needed at runtime because the # server runs TypeScript directly; everything else is production-only. COPY package.json package-lock.json* ./ COPY packages/core/package.json packages/core/ COPY packages/db/package.json packages/db/ COPY packages/prime/package.json packages/prime/ COPY apps/api/package.json apps/api/ COPY apps/mcp/package.json apps/mcp/ COPY apps/piggy/package.json apps/piggy/ RUN npm install --omit=dev --no-audit --no-fund && npm install tsx --no-audit --no-fund COPY packages ./packages COPY apps/api ./apps/api COPY apps/mcp ./apps/mcp COPY apps/piggy ./apps/piggy COPY --from=build /app/apps/web/dist ./apps/web/dist # Run unprivileged. The node image ships a `node` user for exactly this. RUN chown -R node:node /app USER node EXPOSE 8920 # The health endpoint is unauthenticated by design so this works without # credentials baked into the image. HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:8920/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" CMD ["npx", "tsx", "apps/api/src/server.ts"]