# syntax=docker/dockerfile:1.7 # Multi-stage build: # 1. deps — install pnpm + workspace dependencies (cached layer) # 2. build — compile packages/core + web SPA + apps/api TypeScript # 3. runtime — copy artifacts + production deps + built frontend assets ARG NODE_VERSION=20.18.0 # ── Stage 1: deps ────────────────────────────────────────────────────────── FROM node:${NODE_VERSION}-bookworm-slim AS deps ENV PNPM_HOME=/usr/local/pnpm ENV PATH=$PNPM_HOME:$PATH RUN apt-get update \ && apt-get install -y --no-install-recommends python3 make g++ \ && rm -rf /var/lib/apt/lists/* \ && corepack enable \ && corepack prepare pnpm@9.12.3 --activate WORKDIR /app COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./ COPY packages/core/package.json packages/core/ COPY apps/web-legacy/package.json apps/web-legacy/ COPY apps/api/package.json apps/api/ RUN pnpm install --frozen-lockfile # ── Stage 2: build ───────────────────────────────────────────────────────── FROM deps AS build COPY packages/core packages/core COPY apps/web-legacy apps/web-legacy COPY apps/api apps/api RUN pnpm --filter @task-optimizer/core build \ && pnpm --filter @task-optimizer/web-legacy build \ && pnpm --filter @task-optimizer/api build # ── Stage 3: runtime ─────────────────────────────────────────────────────── FROM node:${NODE_VERSION}-bookworm-slim AS runtime ENV NODE_ENV=production ENV PNPM_HOME=/usr/local/pnpm ENV PATH=$PNPM_HOME:$PATH RUN corepack enable \ && corepack prepare pnpm@9.12.3 --activate WORKDIR /app # Workspace manifests + lockfile COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./ COPY packages/core/package.json packages/core/ COPY apps/web-legacy/package.json apps/web-legacy/ COPY apps/api/package.json apps/api/ # Production-only dependencies RUN pnpm install --prod --frozen-lockfile # Compiled artifacts COPY --from=build /app/packages/core/dist packages/core/dist COPY --from=build /app/apps/api/dist apps/api/dist # Built SPA (served as static files) COPY --from=build /app/apps/web-legacy/dist apps/web-legacy/dist # Volume mount target — created here so first boot has the dir even before # a host bind mount happens (e.g. for one-off `docker run` without compose). RUN mkdir -p /app/data/uploads /app/data/exports EXPOSE 3001 # Healthcheck — same endpoint the frontend uses to verify backend reachability HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:3001/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" CMD ["node", "apps/api/dist/server.js"]