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.
Replaces the single long scrollable list of every question with one
card at a time plus arrow navigation, so a student's focus stays on
the current question instead of the whole quiz at once.
Sources could already draw from any number of topics, but the picker
UI only ever showed one topic's questions at a time, forcing a teacher
to lose their place switching between topics while assembling a quiz.
nginx resolves a plain `proxy_pass http://server:8080;` hostname once at
config-load time. On a cold `docker compose up` that creates every
container at once, the `server` container isn't always registered in
Docker's embedded DNS yet by the time the client's nginx starts — nginx
then fails immediately ("host not found in upstream") and doesn't retry,
so it just crash-loops.
Route the target through a variable instead (`set $upstream_server ...;
proxy_pass $upstream_server;`) with an explicit `resolver`, which makes
nginx resolve the hostname lazily per-request via Docker's DNS rather than
once at startup. Also add `depends_on: [server]` on the client service so
it at least doesn't start before the server container exists at all.
Verified with repeated `docker compose down && docker compose up` cycles
locally — this only reliably reproduced starting every container from
nothing simultaneously, which prior local testing hadn't actually done
(the server container had usually stayed running across rebuilds).
- 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.
The client always called a hardcoded http://localhost:5144, which only
worked when browser and server shared the same "localhost" — breaks for
any real remote deployment, since the browser would try to reach that
port on the visitor's own machine instead of the actual server.
Now every environment routes /api/* to the server under the same origin
the page was loaded from, so the client code needs no per-environment URL:
- Docker (client container's own nginx) proxies /api/ to the server
container.
- `npm run dev` (Vite) proxies /api to localhost:5144 via server.proxy.
- Production nginx (reverse proxy + TLS) just needs to forward everything
to the client container, which already knows how to route /api itself.
Also incidentally removes CORS from the picture everywhere, since none of
these setups make a cross-origin request anymore.
On every push/PR: run Domain.Tests, then docker compose build to validate
the full stack still builds. On push to master: also docker compose up -d
to redeploy. COMPOSE_PROJECT_NAME is pinned to ruvdstests so a run from a
runner-managed checkout always targets the existing stack/volumes instead
of spinning up a duplicate under a different project name.
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.