The job checks out into the runner's own workspace, not
/opt/ruvdstests where DEPLOY.md has the human create a .env file — so
docker compose here had no .env to read JWT_SECRET from and failed
outright. POSTGRES_PASSWORD would have silently fallen back to the
compose file's devpassword default instead of erroring, which on
Deploy would have broken auth against the already-initialized pgdata
volume.
Host-mode jobs execute directly on the bare runner container rather
than a per-job container, and actions/checkout is a JS action — it
needs `node` on PATH to run at all, which the runner image doesn't
ship. Failed every run with "Cannot find: node in PATH" before any
other step got a chance to install anything.
- docker-compose.yml: bind client/server ports to 127.0.0.1 only — the
client container is the sole intended public entry point (it proxies
/api/* to the server itself), a host-level nginx sits in front of it in
production. Client__Origin now configurable via CLIENT_ORIGIN env var.
- docs/DEPLOY.md: step-by-step for a fresh Linux target (Docker install,
clone, .env secrets, nginx + certbot).
- docs/CI-CD.md: runner registration for that same target machine, in
"host" mode so the deploy step's `docker compose up -d` acts on the
actual running stack.
Full-stack F# (Domain/Server/Client via Fable+Elmish+Feliz), PostgreSQL
persistence via Dapper, Docker Compose deployment. Student quiz-taking flow
with time-limit enforcement and focus-loss tracking, Teacher question bank
and quiz builder with results analytics, Admin user management.