Production image + Forgejo CI #35

Closed
opened 2026-06-14 23:39:35 +02:00 by myrmidex · 3 comments
Owner

Single-image production build and CI pipeline. This ticket carries M1's exit criteria.

  • Single FrankenPHP/Caddy production image, multi-stage: composer install (no-dev) + vite build (frontend/) → final image serving Symfony (/api) + static dist (else) via Caddy with SPA fallback.
  • Migrations run on container start (small-scale convenience; revisit if multi-instance).
  • Caddy health response for /api/health.
  • Forgejo CI: build + lint + analyse + test the backend, AND build/lint/test the frontend (both halves gated).

Exit (M1): production image builds and runs == current version; dev runs locally equivalently; CI green on both halves.

Single-image production build and CI pipeline. This ticket carries M1's exit criteria. - Single FrankenPHP/Caddy production image, multi-stage: composer install (no-dev) + `vite build` (frontend/) → final image serving Symfony (`/api`) + static dist (else) via Caddy with SPA fallback. - Migrations run on container start (small-scale convenience; revisit if multi-instance). - Caddy health response for `/api/health`. - Forgejo CI: build + lint + analyse + test the backend, AND build/lint/test the frontend (both halves gated). Exit (M1): production image builds and runs == current version; dev runs locally equivalently; CI green on both halves.
myrmidex added this to the v0.3.0 milestone 2026-06-14 23:39:35 +02:00
myrmidex added the
enhancement
label 2026-06-14 23:39:35 +02:00
myrmidex self-assigned this 2026-06-14 23:39:35 +02:00
Author
Owner

E2E in CI — note from #52 (Playwright setup)

The Playwright e2e harness landed in #52: a playwright compose service (--profile e2e, browsers preinstalled in the image) running the auth smoke test against the live stack. When wiring CI:

  • Add a frontend healthcheck + depends_on: { frontend: { condition: service_healthy } } on the playwright service. Currently depends_on: frontend only waits for start, not for Vite to be serving on 5173 — fine on a fast dev machine (Playwright's nav retries absorb the race), but a likely intermittent-failure source under CI load.
  • The image tag (mcr.microsoft.com/playwright:vX.Y.Z-jammy) must stay in sync with @playwright/test in e2e/package.json (exact-pinned) — CI should use the same.
  • Run command: podman-compose ... --profile e2e run --rm playwright.
  • Smoke test hits the real DB with unique-per-run emails (e2e-<ts>@example.com) — consider a dev/CI DB reset step so the user table doesn't accumulate over time.
## E2E in CI — note from #52 (Playwright setup) The Playwright e2e harness landed in #52: a `playwright` compose service (`--profile e2e`, browsers preinstalled in the image) running the auth smoke test against the live stack. When wiring CI: - **Add a `frontend` healthcheck + `depends_on: { frontend: { condition: service_healthy } }`** on the `playwright` service. Currently `depends_on: frontend` only waits for *start*, not for Vite to be serving on 5173 — fine on a fast dev machine (Playwright's nav retries absorb the race), but a likely intermittent-failure source under CI load. - The image tag (`mcr.microsoft.com/playwright:vX.Y.Z-jammy`) must stay in sync with `@playwright/test` in `e2e/package.json` (exact-pinned) — CI should use the same. - Run command: `podman-compose ... --profile e2e run --rm playwright`. - Smoke test hits the real DB with unique-per-run emails (`e2e-<ts>@example.com`) — consider a dev/CI DB reset step so the `user` table doesn't accumulate over time.
Author
Owner

Add a php healthcheck for CI ordering (from #52 finish-phase review)

compose.override.yaml: the php service has no healthcheck, and frontend / playwright only wait on frontend being healthy — nothing waits on php being ready. The FrankenPHP entrypoint blocks on DB + migrations before serving, so it's low-risk locally, but as more e2e specs land (#53–#56) they'll hit /api through the proxy and could race php boot.

For CI: add a php healthcheck (e.g. probe a cheap endpoint) and have frontend + playwright depends_on it with condition: service_healthy, same pattern the frontend healthcheck now uses.

(Note: the frontend healthcheck itself was fixed in #52 — it must probe 127.0.0.1, not localhost, since the container resolves localhost::1 (IPv6) but Vite binds IPv4 only. Use the same 127.0.0.1 form for any php healthcheck if it probes a Node/IPv4-bound port.)

## Add a `php` healthcheck for CI ordering (from #52 finish-phase review) `compose.override.yaml`: the `php` service has no healthcheck, and `frontend` / `playwright` only wait on `frontend` being healthy — nothing waits on `php` being ready. The FrankenPHP entrypoint blocks on DB + migrations before serving, so it's low-risk locally, but as more e2e specs land (#53–#56) they'll hit `/api` through the proxy and could race php boot. For CI: add a `php` healthcheck (e.g. probe a cheap endpoint) and have `frontend` + `playwright` `depends_on` it with `condition: service_healthy`, same pattern the frontend healthcheck now uses. (Note: the frontend healthcheck itself was fixed in #52 — it must probe `127.0.0.1`, not `localhost`, since the container resolves `localhost`→`::1` (IPv6) but Vite binds IPv4 only. Use the same `127.0.0.1` form for any php healthcheck if it probes a Node/IPv4-bound port.)
Author
Owner

Done — committed to release/v0.3.0.

M1 exit criteria met (verified via live prod-image smoke test):

  • Multi-stage prod image builds under podman: frontend_builder (node) compiles the SPA → bundled into the FrankenPHP image; fixed a podman/buildah heredoc-shell incompatibility that previously made the prod image unbuildable.
  • Caddy serves Symfony at /api/* + Mercure, with an SPA fallback (try_files /index.html) — / → SPA, /login → SPA fallback, both 200.
  • Public GET /api/health → 200 {status:ok,db:ok} (503 + server-side log on DB failure).
  • /api/me unauth → 401 with a minimal prod body (APP_DEBUG=0 confirmed — no stack trace leak).
  • Prod entrypoint self-migrates on boot; dev stack runs equivalently.

CI: .forgejo/workflows/ci.yml (backend cs/stan/test + 95% clover gate, frontend lint/format/tsc/coverage, e2e Playwright against the real prod image) on push:release/* + pull_request:main; image.yml builds+pushes the prod image on v* tag only.

Full suite green (249 tests, 100% backend line coverage), PHPStan L6 clean, CS clean, frontend 45 tests green.

Note: the Forgejo CI pipeline itself has not yet executed on the real runner — first release/* push is its first live run.

Done — committed to `release/v0.3.0`. **M1 exit criteria met (verified via live prod-image smoke test):** - Multi-stage prod image builds under podman: `frontend_builder` (node) compiles the SPA → bundled into the FrankenPHP image; fixed a podman/buildah heredoc-shell incompatibility that previously made the prod image unbuildable. - Caddy serves Symfony at `/api/*` + Mercure, with an SPA fallback (`try_files /index.html`) — `/` → SPA, `/login` → SPA fallback, both 200. - Public `GET /api/health` → 200 `{status:ok,db:ok}` (503 + server-side log on DB failure). - `/api/me` unauth → 401 with a minimal prod body (`APP_DEBUG=0` confirmed — no stack trace leak). - Prod entrypoint self-migrates on boot; dev stack runs equivalently. **CI:** `.forgejo/workflows/ci.yml` (backend cs/stan/test + 95% clover gate, frontend lint/format/tsc/coverage, e2e Playwright against the real prod image) on `push:release/*` + `pull_request:main`; `image.yml` builds+pushes the prod image on `v*` tag only. Full suite green (249 tests, 100% backend line coverage), PHPStan L6 clean, CS clean, frontend 45 tests green. Note: the Forgejo CI pipeline itself has not yet executed on the real runner — first `release/*` push is its first live run.
Sign in to join this conversation.
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: lvl0/buckets#35
No description provided.