batchCooking/apps/api/Dockerfile
kyuno053 4b988706cc
fix(docker): n'installe plus/ne build plus Cypress dans l'image de prod (#22)
- README : le dev local cible désormais `docker compose up -d postgres`
  explicitement. `docker-compose.yml` définit aussi le service `app`
  (celui que Portainer build en prod) — sans nom de service,
  `docker compose up -d` construisait et démarrait aussi ce conteneur en
  local, déclenchant un `pnpm install` sur tout le monorepo (donc le
  `cypress` d'apps/web, avec son téléchargement de binaire) pour une image
  dont le dev local n'a pas besoin.
- Dockerfile (apps/api) : `CYPRESS_INSTALL_BINARY=0` sur le stage `build`
  — évite le téléchargement du binaire Cypress (~200 Mo, Electron) à
  chaque build de l'image de prod, qu'il vienne de `docker compose` ou du
  pipeline de release. Le package `cypress` lui-même reste installé (vraie
  entrée du lockfile) ; `pnpm prune --prod` puis un wipe+reinstall complet
  ont été testés pour l'éliminer aussi, mais se sont révélés dangereux
  dans ce monorepo pnpm (prune ignore les node_modules des autres
  packages du workspace ; le wipe+reinstall a fait sauter le binding
  natif compilé d'argon2, crash silencieux au démarrage du conteneur) —
  pas justifié pour ~10 Mo de JS inerte une fois le téléchargement du
  binaire évité.
- apps/api/package.json : déplace `prisma` (le CLI, invoqué directement
  par le `CMD` du conteneur pour `migrate deploy`) de devDependencies
  vers dependencies — classification plus correcte indépendamment du
  point ci-dessus.

Vérifié par un vrai `docker build` + `docker run` contre un Postgres réel
(migrations, seed, démarrage du serveur, signup avec hash argon2 réel).
2026-08-19 09:45:04 +02:00

67 lines
3.8 KiB
Docker

# Debian-based (not alpine) on purpose: avoids musl-vs-glibc native binding
# surprises for argon2/Prisma's engine binaries. Same base image family for
# build and runtime stages, so "native" binaries built in `build` are
# guaranteed compatible with `runtime`.
FROM node:22-slim AS base
# Prisma's query engine needs OpenSSL to be present to detect the right
# binary target; without it, it silently defaults to a guess (openssl-1.1.x)
# that may not match what's actually on the image and fail at runtime.
RUN apt-get update && apt-get install -y --no-install-recommends openssl && rm -rf /var/lib/apt/lists/*
RUN corepack enable
WORKDIR /repo
# Single image serving both the API and the built frontend (apps/web) — one
# process, one container, no separate nginx/static host. Builds both so the
# runtime stage below can copy each app's build output independently.
FROM base AS build
# `pnpm install` (below) installs every workspace's dependencies, including
# apps/web's `cypress` devDependency — this image never runs it, so skip its
# (large, Electron-bundled) binary download: saves build time/bandwidth and
# removes a network dependency on Cypress's CDN from every production build.
# The `cypress` npm *package* itself still ends up in `node_modules` (it's a
# real lockfile entry) and gets copied into `runtime` below — a deliberate
# tradeoff. Tried to prune it out too (`pnpm prune --prod`, then a full
# wipe-and-reinstall with `--prod`), but in this pnpm workspace both proved
# actively unsafe rather than just ineffective: `prune` doesn't cascade into
# sibling workspace projects' `node_modules` at all, and even a *scoped*
# `pnpm --filter web prune --prod` emptied out apps/api's entire
# `node_modules` as a side effect; the wipe-and-reinstall variant silently
# dropped argon2's compiled native binding (no postinstall re-run to rebuild
# it), which only surfaced as a crash *after* deploy
# (`Cannot find module '.../argon2.node'`) — not something to risk on the
# real production image for what's ~10MB of inert JS once the binary
# download above is already skipped.
ENV CYPRESS_INSTALL_BINARY=0
COPY . .
RUN pnpm install --frozen-lockfile
RUN pnpm --filter api build
RUN pnpm --filter web build
# Copies the monorepo structure as-is (not a flattened single package) so
# pnpm's symlinked node_modules (root node_modules/.pnpm <- apps/api/node_modules)
# stay valid — paths must match exactly between build and runtime stages.
FROM base AS runtime
ENV NODE_ENV=production
# Tells the API where to find the built frontend — see FRONTEND_DIST_DIR's
# doc comment in apps/api/src/config/env.ts.
ENV FRONTEND_DIST_DIR=/repo/apps/web/dist
COPY --from=build /repo/node_modules ./node_modules
COPY --from=build /repo/package.json ./package.json
COPY --from=build /repo/pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY --from=build /repo/packages/shared ./packages/shared
COPY --from=build /repo/packages/error-tools ./packages/error-tools
COPY --from=build /repo/packages/express-tools ./packages/express-tools
COPY --from=build /repo/packages/date-tools ./packages/date-tools
COPY --from=build /repo/apps/api/node_modules ./apps/api/node_modules
COPY --from=build /repo/apps/api/dist ./apps/api/dist
COPY --from=build /repo/apps/api/prisma ./apps/api/prisma
COPY --from=build /repo/apps/api/package.json ./apps/api/package.json
COPY --from=build /repo/apps/web/dist ./apps/web/dist
WORKDIR /repo/apps/api
EXPOSE 3000
# Applies pending migrations, then seeds the reference data (Diet/Category/
# Allergy — see src/scripts/seed-runtime.ts) before starting. Both steps are
# safe to repeat on every container start: migrate deploy only applies
# pending migrations, and the seed upserts by unique name.
CMD ["sh", "-c", "node_modules/.bin/prisma migrate deploy && node dist/scripts/seed-runtime.js && node dist/server.js"]