From 7ab43125a190507a2a6d6c30afbfb708623fe83d Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Fri, 29 May 2026 19:46:13 +0200 Subject: [PATCH] =?UTF-8?q?feat(infra):=20dockerised=20full-stack=20dev=20?= =?UTF-8?q?mode=20=E2=80=94=20apps=20compose=20profile=20(ADR-0030)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an 'apps' Compose profile that runs the three Nx dev servers (portal-bff, portal-shell, portal-admin) from a shared Dockerfile.dev, so the whole stack boots with 'dev.sh up apps' and no native Node/pnpm. - Dockerfile.dev: node:24-bookworm + corepack (pnpm from packageManager at runtime), NX_DAEMON=false. No build-time COPY/install. - dev-entrypoint.sh: BFF runs prisma generate + migrate deploy then serves; SPA services go straight to nx serve. - dev.compose.yml: one-shot apps-deps installs into a shared node_modules volume once (apps gate on its completion, no install race); repo bind-mounted, node_modules + .nx in named volumes; BFF reuses its own .env (required:false) with host URLs overridden to Compose service names. - dev.sh: apps added to ALL_PROFILES + usage. - Docs: which-mode-when table (setup doc), apps section (infra README), CLAUDE.md roll-up. ADR-0030 flipped to accepted. Dev-only; production images stay with the ADR-0028 Container Registry work. Additive + profile-gated — native and devcontainer flows unchanged. Full-boot validation pending on a Docker host (see PR body). --- CLAUDE.md | 5 +- docs/decisions/0030-dockerised-dev-mode.md | 4 +- docs/decisions/README.md | 2 +- docs/setup/01-dev-debian-vm-setup.md | 18 +++++ infra/README.md | 40 ++++++++-- infra/local/Dockerfile.dev | 35 ++++++++ infra/local/dev-entrypoint.sh | 26 ++++++ infra/local/dev.compose.yml | 93 ++++++++++++++++++++++ infra/local/dev.sh | 7 +- 9 files changed, 216 insertions(+), 14 deletions(-) create mode 100644 infra/local/Dockerfile.dev create mode 100755 infra/local/dev-entrypoint.sh diff --git a/CLAUDE.md b/CLAUDE.md index 898ea4e..4616ccb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,12 +57,13 @@ The structural, security, observability, and quality choices are recorded as ADR - **Portal-side identity model:** `Person` golden record (stable identity, can exist without a portal account — workforce pre-provisioning, dossier bénéficiaires, alumni) + `User` overlay (one-to-zero-or-one with Person, lazy-created on first OIDC callback in v1; carries portal-only state like `lastSignInAt`). `UserScope` backs the ADR-0025 scope axis with opaque `value` strings referencing ADR-0027's `Structure.code` / `Delegation.code` / `Region.code` — no FK at the DB level so historical rows survive structure decommissioning; admin-UI write path validates. v1 dedup uses `entraOid` only; `Person.email` is an indexed attribute, not a unique key, because two distinct humans genuinely share emails (shared aliases, generic `info@`, upstream-feed errors). Facets (Salarié / Élu / Adhérent / Bénéficiaire) + Pléiades / Acteurs+ sync + operator-confirmed Person-merge flow deferred to ADR-0029 — see [ADR-0026](docs/decisions/0026-person-user-portal-data-model.md). - **Portal-side organisational hierarchy:** `Region` (INSEE 2-digit) → `Delegation` (department 2–3-char) → `Structure` with `kind` discriminator (`medico_social` / `antenne` / `dispositif` / `entreprise_adaptee` / `mouvement` / `administratif` / `siege`, aligned with cascade's `Structure.type`). `Structure.code` is the portal-internal string PK, externally meaningful: for medico-social rows it equals the FINESS (9 digits) and round-trips cleanly through scope literals (`etablissement:0330800013`) and URLs; for non-FINESS rows it is an APF-internal slug (`siege`, `apf-bdx-merignac`, `ea-toulouse`, …). `finess` / `siret` / `codePaie` are nullable, unique-when-present attributes — populated where the upstream registry has the structure on file. v1 ships a small inline-migration seed (test-tenant scope: Région Nouvelle-Aquitaine, Délégation 33, a handful of médico-social + siège); the full cascade-driven inventory sync, plus `Pole` / `Service` / arbitrary nesting / per-source enrichment, land additively with ADR-0029 — see [ADR-0027](docs/decisions/0027-portal-side-organisational-hierarchy.md). - **Runtime:** Node.js latest LTS major. +- **Local dev environment:** three coexisting run modes — native `pnpm nx serve`, the VSCode devcontainer, and a Docker Compose `apps` profile that boots all three Nx dev servers without a native Node/pnpm toolchain (`./infra/local/dev.sh up apps`). Dev-only (production images deferred to the ADR-0028 Container Registry work); shared `Dockerfile.dev`, repo bind-mounted for hot reload, `node_modules`/Nx cache in named volumes, BFF entrypoint runs `prisma generate` + `migrate deploy` — see [ADR-0030](docs/decisions/0030-dockerised-dev-mode.md). ## Repository status The Nx workspace is **scaffolded and operational**. The three apps (`portal-shell`, `portal-admin`, `portal-bff`) and the seven lib roots (`libs/feature/auth`, `libs/shared/auth`, `libs/shared/charts`, `libs/shared/state`, `libs/shared/tokens`, `libs/shared/ui`, `libs/shared/util`) are in place; CI runs `format:check / lint / test / build` plus the ADR-0025 catalogue-drift gate on every PR. -ADRs 0001 → 0028 are accepted and cover the structural, security, observability, quality, i18n, admin-app, docs-site, charts, AI-relay, authorization, portal-side identity + organisational hierarchy, and CI/CD platform migration choices. ADR-0028 supersedes only ADR-0015's "Gitea Actions" platform choice — the rest of ADR-0015's architectural principles carry over unchanged; the 4-phase migration (mirror → parallel pipelines → cutover → cleanup) ships in follow-up PRs. ADR-0029 (cascade / Pléiades / Acteurs+ syncs + facet schemas) is the next proposed addition. Until ADR-0026 + ADR-0027 implementation PRs ship, ADR-0025's stubs (`Principal.user.{id, personId}` placeholders, `StubScopeResolver`'s `unrestricted` blanket return) remain in place. **Shipped on `main`:** +ADRs 0001 → 0028 plus ADR-0030 are accepted and cover the structural, security, observability, quality, i18n, admin-app, docs-site, charts, AI-relay, authorization, portal-side identity + organisational hierarchy, CI/CD platform migration, and dockerised local-dev mode choices. ADR-0028 supersedes only ADR-0015's "Gitea Actions" platform choice — the rest of ADR-0015's architectural principles carry over unchanged; the 4-phase migration (mirror → parallel pipelines → cutover → cleanup) ships in follow-up PRs. ADR-0029 (cascade / Pléiades / Acteurs+ syncs + facet schemas) is the next proposed addition — its number is reserved ahead of ADR-0030, which was written first. Until ADR-0026 + ADR-0027 implementation PRs ship, ADR-0025's stubs (`Principal.user.{id, personId}` placeholders, `StubScopeResolver`'s `unrestricted` blanket return) remain in place. **Shipped on `main`:** - **Phase-1 foundation** — Nx workspace, Angular `portal-shell`, NestJS `portal-bff`, Prisma + Postgres, Pino + OpenTelemetry, Husky/lint-staged/commitlint, Gitea Actions CI. - **Phase-2 auth + audit + security** — OIDC Auth Code + PKCE via MSAL Node, Redis sessions with AES-256-GCM at rest, idle 30 min sliding + absolute 12 h hard ceiling, RP-initiated logout, double-submit CSRF, `audit.events` append-only schema with role-based grants, helmet + env-driven CORS allowlist + rate limiting + structured error envelope (see [ADR-0021](docs/decisions/0021-phase-2-security-baseline.md)). @@ -109,7 +110,7 @@ pnpm nx format:check ## Environment conventions - **Two development environments.** `local` (Windows-WSL or native macOS / Linux on the workstation) and `development` (Debian 13 VM at `10.100.201.21` — the default for new devs, replaces WSL). A `hybrid` sub-mode runs the IDE + Nx servers on the workstation while reaching the infra services (postgres / redis / otel) on the dev VM through SSH tunnels. Full procedure: [docs/setup/01-dev-debian-vm-setup.md](docs/setup/01-dev-debian-vm-setup.md). The legacy WSL flow remains documented in [docs/setup/02-wsl-terminal-setup.md](docs/setup/02-wsl-terminal-setup.md). -- **Two IDE flows on the dev VM** — VSCode Remote-SSH (default; transparent equivalent of the WSL flow) and Devcontainer (`.devcontainer/devcontainer.json` shipped, Node + pnpm pinned in the image, mounts the docker socket so the host's `apf-portal-dev` Compose network is reachable from inside). +- **Two IDE flows on the dev VM** — VSCode Remote-SSH (default; transparent equivalent of the WSL flow) and Devcontainer (`.devcontainer/devcontainer.json` shipped, Node + pnpm pinned in the image, mounts the docker socket so the host's `apf-portal-dev` Compose network is reachable from inside). Independently of the IDE flow, the apps can run **natively** (`pnpm nx serve`) or as **Docker Compose services** (`./infra/local/dev.sh up apps`, no native toolchain — ADR-0030); the "which mode when" table is in [docs/setup/01-dev-debian-vm-setup.md](docs/setup/01-dev-debian-vm-setup.md). - **Never install Angular globally.** Use `pnpm dlx` for one-off CLI invocations and project-local `pnpm nx ...` for everything else — versions stay pinned per project. - **On WSL: work inside the WSL filesystem** (`~/Works/...`), never under `/mnt/c` — the latter has severe I/O penalties that break Nx caching and dev-server reload times. On the dev VM the analogous rule is "stay on the VM disk, do not work over SSHFS / network mounts". - **pnpm is mandatory** (activated via `corepack enable`); do not introduce npm or yarn lockfiles. diff --git a/docs/decisions/0030-dockerised-dev-mode.md b/docs/decisions/0030-dockerised-dev-mode.md index 49e7675..7156b9b 100644 --- a/docs/decisions/0030-dockerised-dev-mode.md +++ b/docs/decisions/0030-dockerised-dev-mode.md @@ -1,5 +1,5 @@ --- -status: proposed +status: accepted date: 2026-05-28 decision-makers: R&D Lead tags: [infrastructure, process] @@ -109,4 +109,4 @@ The follow-up PR documents this table in `docs/setup/`: - The BFF entrypoint's `prisma generate` + `migrate deploy` follows [ADR-0006](0006-persistence-postgresql-prisma.md); migrations are applied, never authored, inside the container. - Production images are **out of scope** and tracked against the [ADR-0028](0028-migrate-cicd-and-git-hosting-to-gitlab.md) Container Registry follow-up (post-cutover). - Builds on the existing [`infra/local/dev.compose.yml`](../../infra/local/dev.compose.yml) profiles pattern (`dbtools`, `observability`, `serve-static`) — `apps` is one more profile in the same idiom. -- Status is `proposed`; on acceptance, update the CLAUDE.md architecture roll-up and add the "which mode when" guidance to `docs/setup/`. +- Accepted; the implementation PR carries the `Dockerfile.dev`, the `apps` Compose profile, the BFF entrypoint, the CLAUDE.md architecture roll-up entry, and the "which mode when" guidance in `docs/setup/`. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 01851a3..c2db0bb 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -72,4 +72,4 @@ ADRs are listed in numerical order. To slice by topic, filter on the `Tags` colu | [0026](0026-person-user-portal-data-model.md) | `Person` golden record + `User` portal-account — portal-side identity model | accepted | `data`, `backend`, `security` | 2026-05-24 | | [0027](0027-portal-side-organisational-hierarchy.md) | Portal-side organisational hierarchy — `Structure` with kind discriminator and nullable FINESS / SIRET | accepted | `data`, `backend` | 2026-05-24 | | [0028](0028-migrate-cicd-and-git-hosting-to-gitlab.md) | Migrate CI/CD + git hosting from Gitea to GitLab self-hosted | accepted | `infrastructure`, `process` | 2026-05-26 | -| [0030](0030-dockerised-dev-mode.md) | Dockerised full-stack dev mode — `compose up` runs the Nx apps alongside infra | proposed | `infrastructure`, `process` | 2026-05-28 | +| [0030](0030-dockerised-dev-mode.md) | Dockerised full-stack dev mode — `compose up` runs the Nx apps alongside infra | accepted | `infrastructure`, `process` | 2026-05-28 | diff --git a/docs/setup/01-dev-debian-vm-setup.md b/docs/setup/01-dev-debian-vm-setup.md index ac73680..576c704 100644 --- a/docs/setup/01-dev-debian-vm-setup.md +++ b/docs/setup/01-dev-debian-vm-setup.md @@ -203,6 +203,24 @@ pnpm exec nx run-many -t lint test --parallel=3 # smoke test ## Step 5 — Choose your IDE flow +### Running the apps — three modes (which when) + +There are three ways to run the apps locally. They coexist; pick by need. + +| Mode | Toolchain on host | Best for | +| --------------------------------- | ------------------ | ----------------------------------------------------------------------------------------- | +| **Native** (`pnpm nx serve`) | Node + pnpm native | Day-to-day dev — fastest iteration, simplest debugger attach. | +| **Devcontainer** (Option B below) | none (Docker) | IDE-integrated dev with no native toolchain; VSCode attaches, you run `nx serve` inside. | +| **Compose `apps` profile** | none (Docker) | "Run everything with one command" — onboarding, frontend-only work, demos. No IDE attach. | + +The Compose `apps` profile (per [ADR-0030](../decisions/0030-dockerised-dev-mode.md)) is the lightest way to boot the full stack without a native toolchain: + +```bash +./infra/local/dev.sh up apps # infra + portal-bff:3000 + portal-shell:4200 + portal-admin:4300 +``` + +Usage, prerequisites (the BFF still needs its `apps/portal-bff/.env` secrets) and the port caveat are documented in [infra/README.md](../../infra/README.md) → "Dockerised app dev mode". The IDE-flow options below (Remote-SSH / Devcontainer / Hybrid) are orthogonal — they decide where your editor + terminals run, not how the apps boot. + ### Option A — VSCode Remote-SSH (default) From your workstation: diff --git a/infra/README.md b/infra/README.md index 7d6dddd..bedc9bb 100644 --- a/infra/README.md +++ b/infra/README.md @@ -143,14 +143,16 @@ Old, no-longer-referenced images can be reaped during the periodic `docker syste A Docker Compose recipe spinning up the runtime services the BFF and ADRs assume — Postgres, Redis, OpenTelemetry Collector — plus optional viewers / tooling (pgweb, Jaeger UI, Caddy serve-static) gated behind Compose profiles. Designed to start in a single command on a contributor's WSL2 / Linux / macOS host. -| File | Role | -| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -| [`local/dev.sh`](local/dev.sh) | Convenience wrapper around `docker compose` — see "Convenience script" below | -| [`local/dev.compose.yml`](local/dev.compose.yml) | Service definitions: postgres, redis, otel-collector, plus pgweb / jaeger / caddy behind profiles | -| [`local/.env.example`](local/.env.example) | Credentials + ports template (copy to `.env`, which is git-ignored) | -| [`local/init/postgres/01-init.sql`](local/init/postgres/01-init.sql) | Bootstrap SQL for ADR-0013: audit roles + schema, applied on first boot only | -| [`local/otel-collector.yaml`](local/otel-collector.yaml) | Collector pipeline: OTLP receivers → batch → debug exporter (always) + forward to Jaeger when active | -| [`local/Caddyfile`](local/Caddyfile) | Reverse-proxy config for the `serve-static` profile — per-locale SPA fallback + smart `/` redirect (ADR-0019) | +| File | Role | +| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| [`local/dev.sh`](local/dev.sh) | Convenience wrapper around `docker compose` — see "Convenience script" below | +| [`local/dev.compose.yml`](local/dev.compose.yml) | Service definitions: postgres, redis, otel-collector, plus pgweb / jaeger / caddy / the `apps` dev servers behind profiles | +| [`local/Dockerfile.dev`](local/Dockerfile.dev) | Dev-only image (Node 24 + corepack) shared by the three `apps`-profile dev servers (ADR-0030) | +| [`local/dev-entrypoint.sh`](local/dev-entrypoint.sh) | Entrypoint for the `apps` services: BFF runs `prisma generate` + `migrate deploy`, then each runs `nx serve` | +| [`local/.env.example`](local/.env.example) | Credentials + ports template (copy to `.env`, which is git-ignored) | +| [`local/init/postgres/01-init.sql`](local/init/postgres/01-init.sql) | Bootstrap SQL for ADR-0013: audit roles + schema, applied on first boot only | +| [`local/otel-collector.yaml`](local/otel-collector.yaml) | Collector pipeline: OTLP receivers → batch → debug exporter (always) + forward to Jaeger when active | +| [`local/Caddyfile`](local/Caddyfile) | Reverse-proxy config for the `serve-static` profile — per-locale SPA fallback + smart `/` redirect (ADR-0019) | ### First-time setup @@ -191,6 +193,7 @@ Run `./infra/local/dev.sh help` for the full reference. Cheat-sheet: | `./infra/local/dev.sh up dbtools` | Core + pgweb | | `./infra/local/dev.sh up observability` | Core + Jaeger | | `./infra/local/dev.sh up serve-static` | Core + Caddy serving `dist/.../browser/` per ADR-0019 | +| `./infra/local/dev.sh up apps` | Core + the three Nx dev servers in Docker (ADR-0030) | | `./infra/local/dev.sh down` | Tear down the whole stack (every profile in scope) | | `./infra/local/dev.sh down -v` | Tear down + wipe named volumes (incl. audit-roles bootstrap) | | `./infra/local/dev.sh stop pgweb` | Stop one service (containers stay around) | @@ -202,6 +205,27 @@ Anything not matching one of the named verbs is passed through to `docker compos If you prefer to call `docker compose` directly, every example below shows the raw command alongside the script form. +### Dockerised app dev mode — `apps` profile (ADR-0030) + +The `apps` profile runs the three Nx dev servers **in Docker**, so a contributor can bring up the whole stack without installing Node / pnpm natively: + +```bash +./infra/local/dev.sh up apps # infra + portal-bff:3000 + portal-shell:4200 + portal-admin:4300 +``` + +How it works (see [ADR-0030](../docs/decisions/0030-dockerised-dev-mode.md)): + +- A single [`Dockerfile.dev`](local/Dockerfile.dev) (Node 24 + corepack) backs all three services — one image, one install for the monorepo. +- The repo is bind-mounted for hot reload; `node_modules` and the Nx cache live in named volumes (`apf-portal-app-node-modules`, `apf-portal-app-nx-cache`) so the container's native modules are never shadowed by the host's. +- A one-shot `apps-deps` service runs `pnpm install` once into the shared volume; the three servers gate on its completion, avoiding a three-way install race. +- The BFF entrypoint runs `prisma generate` + `prisma migrate deploy` before serving. + +**Prerequisite — the BFF still needs its secrets.** No native toolchain is required, but `apps/portal-bff/.env` (Entra / session / jwks config) must exist, same as native dev (`cp apps/portal-bff/.env.example apps/portal-bff/.env` then fill it). The host-specific URLs (`DATABASE_URL` / `REDIS_URL` / OTel endpoint) are overridden automatically to the Compose service names — you don't edit those for the container. SPA-only work (`up portal-shell`) doesn't need the BFF env. + +**Port note.** The SPA dev servers default to 4200 / 4300 — 4200 is the same port the `serve-static` profile uses. Don't run `apps` and `serve-static` together, or set `SHELL_PORT` in `infra/local/.env`. + +The three dev modes (native `nx serve`, devcontainer, this `apps` profile) and when to use each are summarised in [docs/setup/01-dev-debian-vm-setup.md](../docs/setup/01-dev-debian-vm-setup.md). + ### Service endpoints (defaults) | Service | Host port | Purpose | diff --git a/infra/local/Dockerfile.dev b/infra/local/Dockerfile.dev new file mode 100644 index 0000000..b4bfbb2 --- /dev/null +++ b/infra/local/Dockerfile.dev @@ -0,0 +1,35 @@ +# Dev-only image for the dockerised full-stack dev mode (ADR-0030). +# +# One image serves all three Nx apps (portal-bff / portal-shell / +# portal-admin) — the monorepo means a single install. The image is +# intentionally minimal: Node + corepack, nothing copied in. The repo +# is bind-mounted at runtime and dependencies install into a named +# volume (see dev.compose.yml `apps` profile), so node_modules — with +# its native modules built for THIS image — is never shadowed by the +# host's. +# +# NOT a production image. Production artefacts are out of scope per +# ADR-0030 and tracked against the ADR-0028 Container Registry work. +FROM node:24-bookworm + +# corepack ships with Node 24. Enabling it lets pnpm resolve from the +# `packageManager` field in package.json at runtime — no version pinned +# here, so this image never drifts from the repo's pinned pnpm. +RUN corepack enable + +# Nx + Angular CLI memory ceiling — matches the devcontainer (.devcontainer/ +# devcontainer.json). 4 GB avoids OOM on large `nx` graph work. +ENV NODE_OPTIONS=--max-old-space-size=4096 +# Nx daemon is per-container and would contend across the three app +# containers sharing one workspace bind-mount — disable it. Slight +# task-graph cost, robust behaviour. +ENV NX_DAEMON=false + +WORKDIR /workspace + +# No COPY / no install at build time: the repo is bind-mounted and the +# `apps-deps` one-shot service installs into the node_modules volume on +# first boot. Build context stays tiny (just infra/local/) — nothing is +# copied. Invoked via `bash` so it does not depend on the bind-mounted +# script keeping its executable bit. +ENTRYPOINT ["bash", "/workspace/infra/local/dev-entrypoint.sh"] diff --git a/infra/local/dev-entrypoint.sh b/infra/local/dev-entrypoint.sh new file mode 100755 index 0000000..e3d8d84 --- /dev/null +++ b/infra/local/dev-entrypoint.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Shared entrypoint for the ADR-0030 `apps` Compose profile services. +# +# Behaviour is driven by env + the passed command: +# - The `apps-deps` one-shot service runs `pnpm install` as its +# command; this entrypoint just execs it (APF_ROLE unset → no +# prisma). It populates the shared node_modules volume once, so +# the app services don't race three concurrent installs. +# - The BFF service sets APF_ROLE=bff → this entrypoint runs +# `prisma generate` + `prisma migrate deploy` (client + committed +# migrations) before serving. `migrate deploy`, never `migrate +# dev` — a container never authors migrations. +# - The SPA services (portal-shell / portal-admin) leave APF_ROLE +# unset → straight to `exec "$@"` (nx serve). +set -euo pipefail +cd /workspace + +if [[ "${APF_ROLE:-}" == "bff" ]]; then + echo "[dev-entrypoint] BFF: prisma generate" + pnpm exec prisma generate + echo "[dev-entrypoint] BFF: prisma migrate deploy" + pnpm exec prisma migrate deploy +fi + +echo "[dev-entrypoint] exec: $*" +exec "$@" diff --git a/infra/local/dev.compose.yml b/infra/local/dev.compose.yml index 5e6af64..37d53c6 100644 --- a/infra/local/dev.compose.yml +++ b/infra/local/dev.compose.yml @@ -34,6 +34,23 @@ name: apf-portal-dev +# Shared base for the ADR-0030 `apps` profile — dev servers for the three +# Nx apps. The repo is bind-mounted for hot reload; node_modules and the +# Nx cache live in named volumes so the container's install (native +# modules built for THIS image) is never shadowed by the host's. +x-app-base: &app-base + build: + context: . + dockerfile: Dockerfile.dev + profiles: [apps] + working_dir: /workspace + volumes: + - ../../:/workspace + - app-node-modules:/workspace/node_modules + - app-nx-cache:/workspace/.nx + networks: + - apf-portal-dev + services: postgres: image: postgres:17.2-alpine @@ -189,11 +206,87 @@ services: networks: - apf-portal-dev + # ------------------------------------------------------------------ + # ADR-0030 dockerised full-stack dev mode (`--profile apps`). + # + # ./infra/local/dev.sh up apps → infra + the three dev servers, + # no native Node/pnpm on the host. + # + # Hot reload via the repo bind-mount. See docs/setup/ for the + # "which mode when" guidance (native / devcontainer / compose apps). + # NOTE: the SPA dev servers default to 4200/4300 — the same 4200 the + # `serve-static` profile uses; don't run `apps` and `serve-static` + # together, or override SHELL_PORT. + # ------------------------------------------------------------------ + + # One-shot: populate the shared node_modules volume once so the three + # app services don't race three concurrent installs on it. Exits when + # done; the apps gate on its successful completion. + apps-deps: + <<: *app-base + container_name: apf-portal-apps-deps + command: ['pnpm', 'install', '--frozen-lockfile'] + + portal-bff: + <<: *app-base + container_name: apf-portal-bff-dev + environment: + APF_ROLE: bff + NODE_ENV: development + PORT: '3000' + # Host-specific URLs rebuilt from infra/local/.env creds → Compose + # service names. Compose `environment` wins over `env_file`, so + # these override the localhost values in the BFF's own .env. + DATABASE_URL: 'postgresql://${POSTGRES_USER:-portal}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-portal_dev}?schema=public' + REDIS_URL: 'redis://default:${REDIS_PASSWORD}@redis:6379/0' + OTEL_EXPORTER_OTLP_ENDPOINT: 'http://otel-collector:4318/v1/traces' + # Secrets (Entra / session / jwks) come from the BFF's own dev env. + # `required: false` so SPA-only devs can `up` without it — the BFF + # then fails its own boot-time config validators with a clear message + # rather than Compose erroring on a missing file. + env_file: + - path: ../../apps/portal-bff/.env + required: false + command: ['pnpm', 'exec', 'nx', 'serve', 'portal-bff'] + ports: + - '${BFF_PORT:-3000}:3000' + depends_on: + apps-deps: + condition: service_completed_successfully + postgres: + condition: service_healthy + redis: + condition: service_healthy + + portal-shell: + <<: *app-base + container_name: apf-portal-shell-dev + command: ['pnpm', 'exec', 'nx', 'serve', 'portal-shell', '--host', '0.0.0.0', '--port', '4200'] + ports: + - '${SHELL_PORT:-4200}:4200' + depends_on: + apps-deps: + condition: service_completed_successfully + + portal-admin: + <<: *app-base + container_name: apf-portal-admin-dev + command: ['pnpm', 'exec', 'nx', 'serve', 'portal-admin', '--host', '0.0.0.0', '--port', '4300'] + ports: + - '${ADMIN_PORT:-4300}:4300' + depends_on: + apps-deps: + condition: service_completed_successfully + volumes: postgres-data: name: apf-portal-postgres-data redis-data: name: apf-portal-redis-data + app-node-modules: + name: apf-portal-app-node-modules + app-nx-cache: + name: apf-portal-app-nx-cache networks: apf-portal-dev: diff --git a/infra/local/dev.sh b/infra/local/dev.sh index ffb81e4..febaa77 100755 --- a/infra/local/dev.sh +++ b/infra/local/dev.sh @@ -16,7 +16,7 @@ COMPOSE_FILE="${SCRIPT_DIR}/dev.compose.yml" # Profiles defined in dev.compose.yml. Keep in sync if a new profile # is added. -ALL_PROFILES=(dbtools observability serve-static) +ALL_PROFILES=(dbtools observability serve-static apps) # Build "--profile p1 --profile p2 …" as separate arguments. build_all_profile_flags() { @@ -60,6 +60,10 @@ Commands: dbtools core + pgweb observability core + jaeger serve-static core + caddy (production-build reverse proxy) + apps core + the three Nx dev servers in + Docker — no native Node/pnpm needed + (ADR-0030). portal-bff:3000, + portal-shell:4200, portal-admin:4300. Multiple targets allowed (e.g. `up dbtools observability`). down [-v] Tear the stack down. Always runs with every @@ -87,6 +91,7 @@ Commands: Examples: ./infra/local/dev.sh up ./infra/local/dev.sh up all + ./infra/local/dev.sh up apps ./infra/local/dev.sh up observability ./infra/local/dev.sh down -v ./infra/local/dev.sh stop pgweb