Bring the API and WebUI into Docker Compose behind a full-stack profile #122

Merged
rob merged 1 commit from feat/compose-full-stack into main 2026-08-11 14:36:31 +00:00
Owner

Task 109. docker compose up alone is unchanged — still Postgres only. docker compose --profile full up -d --build --wait adds api and webui, built from task 108's own Dockerfiles.

The trap: Api__BaseAddress is fetched by the browser, not resolved inside the Compose network, so it's set to the API's own published host port (http://localhost:8081 by default) — not http://api:8080, which only resolves between containers and would fail in the browser with a network error. The API's own ConnectionStrings__PlaceMark correctly does use the Docker-network hostname (Host=postgres), since that connection is server-side.

CORS: Cors__AllowedOrigins__0 is computed from the same WEBUI_PORT the WebUI is published on, so overriding one can't silently leave the other behind.

ADR-0106.

Verified by running, not by docker compose config validating: built and brought up an isolated instance, provisioned roles and applied the schema against it, then drove a real headless browser through http://localhost:8080 — registered an account, signed in, reached the authenticated map — end to end through the actual Compose-published origin. Confirmed separately that plain docker compose up (no profile) still starts only Postgres.

Environment variables documented in .env.example and the README's new "Full local stack" section: API_PORT/WEBUI_PORT, PLACEMARK_APP_ROLE_PASSWORD (shared with the existing provisioning step), and Jwt__SigningKey/Oidc__* (defaulted to the same published throwaways user-secrets.example.json already documents).

Full suite, architecture tests, coverage ratchet and dotnet format all clean on the pinned SDK.

Out of scope, per the ticket: registry push, hosting, TLS, CD.

Task 109. `docker compose up` alone is unchanged — still Postgres only. `docker compose --profile full up -d --build --wait` adds `api` and `webui`, built from task 108's own Dockerfiles. **The trap**: `Api__BaseAddress` is fetched by the browser, not resolved inside the Compose network, so it's set to the API's own **published host port** (`http://localhost:8081` by default) — not `http://api:8080`, which only resolves between containers and would fail in the browser with a network error. The API's own `ConnectionStrings__PlaceMark` correctly *does* use the Docker-network hostname (`Host=postgres`), since that connection is server-side. **CORS**: `Cors__AllowedOrigins__0` is computed from the same `WEBUI_PORT` the WebUI is published on, so overriding one can't silently leave the other behind. ADR-0106. Verified by running, not by `docker compose config` validating: built and brought up an isolated instance, provisioned roles and applied the schema against it, then drove a real headless browser through `http://localhost:8080` — registered an account, signed in, reached the authenticated map — end to end through the actual Compose-published origin. Confirmed separately that plain `docker compose up` (no profile) still starts only Postgres. Environment variables documented in `.env.example` and the README's new "Full local stack" section: `API_PORT`/`WEBUI_PORT`, `PLACEMARK_APP_ROLE_PASSWORD` (shared with the existing provisioning step), and `Jwt__SigningKey`/`Oidc__*` (defaulted to the same published throwaways `user-secrets.example.json` already documents). Full suite, architecture tests, coverage ratchet and `dotnet format` all clean on the pinned SDK. Out of scope, per the ticket: registry push, hosting, TLS, CD.
Bring API and WebUI into Compose behind a full-stack profile
All checks were successful
CI / build (pull_request) Successful in 3m8s
CI / e2e (pull_request) Successful in 1m39s
1c8e1761ae
rob left a comment

Verdict: mergeable

Verified by running, not just reading:

  • Isolated stack (docker compose -p pr122review), custom .env with non-default API_PORT=18081/WEBUI_PORT=18080. curl http://127.0.0.1:18080/appsettings.json on the running WebUI container returned "Api":{"BaseAddress":"http://localhost:18081"} — the published host port, never http://api:8080. Registered a real account through http://127.0.0.1:18081/api/auth/register with Origin: http://localhost:18080 (the overridden WEBUI_PORT) — 201, with Access-Control-Allow-Origin: http://localhost:18080 echoed back; the same request with Origin: http://localhost:9999 got no ACAO header. Both claims hold under an override, not just at the defaults.
  • docker compose config (no profile) lists only postgres; brought the stack up with no profile and only Postgres started. Then --profile full up -d --wait brought up all three healthy, api/webui both running as non-root (uid=1654(app)/uid=101(nginx) from docker exec ... id).
  • Jwt__SigningKey/Oidc__* throwaway defaults in docker-compose.yml are byte-for-byte the same values already published in user-secrets.example.json — no new secret-shaped value introduced.
  • Every env var actually referenced in docker-compose.yml (API_PORT, WEBUI_PORT, PLACEMARK_APP_ROLE_PASSWORD, Jwt__SigningKey, Oidc__Authority/ClientId/ClientSecret) is documented in .env.example; Oidc__RedirectUri/WebUiRedirectUri are deliberately not listed as independently overridable, consistent with them being computed from the two ports.
  • CI run #535 green on 1c8e176; head unchanged from the SHA given.

Nothing else to flag.

Verdict: mergeable Verified by running, not just reading: - Isolated stack (`docker compose -p pr122review`), custom `.env` with non-default `API_PORT=18081`/`WEBUI_PORT=18080`. `curl http://127.0.0.1:18080/appsettings.json` on the running WebUI container returned `"Api":{"BaseAddress":"http://localhost:18081"}` — the published host port, never `http://api:8080`. Registered a real account through `http://127.0.0.1:18081/api/auth/register` with `Origin: http://localhost:18080` (the overridden WEBUI_PORT) — 201, with `Access-Control-Allow-Origin: http://localhost:18080` echoed back; the same request with `Origin: http://localhost:9999` got no ACAO header. Both claims hold under an override, not just at the defaults. - `docker compose config` (no profile) lists only `postgres`; brought the stack up with no profile and only Postgres started. Then `--profile full up -d --wait` brought up all three healthy, `api`/`webui` both running as non-root (`uid=1654(app)`/`uid=101(nginx)` from `docker exec ... id`). - `Jwt__SigningKey`/`Oidc__*` throwaway defaults in `docker-compose.yml` are byte-for-byte the same values already published in `user-secrets.example.json` — no new secret-shaped value introduced. - Every env var actually referenced in `docker-compose.yml` (`API_PORT`, `WEBUI_PORT`, `PLACEMARK_APP_ROLE_PASSWORD`, `Jwt__SigningKey`, `Oidc__Authority`/`ClientId`/`ClientSecret`) is documented in `.env.example`; `Oidc__RedirectUri`/`WebUiRedirectUri` are deliberately not listed as independently overridable, consistent with them being computed from the two ports. - CI run #535 green on `1c8e176`; head unchanged from the SHA given. Nothing else to flag.
rob merged commit 29d4e2a956 into main 2026-08-11 14:36:31 +00:00
rob deleted branch feat/compose-full-stack 2026-08-11 14:36:31 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rob/PlaceMark!122
No description provided.