feat(infra): dockerised full-stack dev mode — apps compose profile (ADR-0030)
CI / scan (pull_request) Successful in 3m46s
CI / commits (pull_request) Successful in 3m46s
CI / check (pull_request) Successful in 4m4s
CI / a11y (pull_request) Successful in 4m25s
Docs site / build (pull_request) Successful in 4m37s
CI / perf (pull_request) Successful in 8m43s
CI / scan (pull_request) Successful in 3m46s
CI / commits (pull_request) Successful in 3m46s
CI / check (pull_request) Successful in 4m4s
CI / a11y (pull_request) Successful in 4m25s
Docs site / build (pull_request) Successful in 4m37s
CI / perf (pull_request) Successful in 8m43s
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).
This commit is contained in:
@@ -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"]
|
||||
Executable
+26
@@ -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 "$@"
|
||||
@@ -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:
|
||||
|
||||
+6
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user