Deployment
Léigh Dom deploys like every other user-facing ABAIR app: CI builds an image on docker-build, pushes it to the internal registry, and SSHes into services-user to restart the stack. Merging to main is the whole release process.
| Repository | phonlab-tcd/leigh-dom |
| Image | 10.0.0.12:5000/leigh-dom:main |
| Host | services-user (10.0.0.18), port 3015 → container 3000 |
| Compose stack | /opt/apps/leigh-dom/ |
| Public URL | https://leighdom.abair.ie |
| Stage | Trigger | What happens |
|---|---|---|
| CI | PR to main | npm ci, typecheck, lint, tests with coverage, and a production next build. |
| CD — build | Push to main | Builds the image on the self-hosted runner and pushes it to the internal registry. |
| CD — deploy | After the build job | ssh deploy@10.0.0.18 "cd /opt/apps/leigh-dom && docker compose pull && docker compose up -d". |
There is no registry login step: the github-runner user on docker-build is pre-authenticated by setup-docker-build.yml. Both jobs run in the prod GitHub environment, which holds the build-time variables.
Configuration
NEXT_PUBLIC_* values referenced from client components are inlined into the browser bundle by next build — setting them only in the compose environment: block leaves the browser with undefined. The sign-in link, account menu and 401 recovery all run client-side, so their config has to arrive as build args.
| Variable | Set where | Notes |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Build arg | From vars.* in the prod environment |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Build arg | Public by design |
NEXT_PUBLIC_AUTH_BASE_URL | Build arg | https://auth.abair.ie |
NEXT_PUBLIC_SITE_URL | Both | Read again server-side by the middleware and /auth/callback, so the two must match |
PUBLIC_ORIGIN | Compose | https://leighdom.abair.ie |
RETENTION_DAYS | Compose | 30 |
Everything else keeps the defaults in lib/config.ts.
PUBLIC_ORIGIN is the one value that must not be left at its default. It is sent as Origin and Referer to synthesis.abair.ie, which rejects an empty or mismatched value — its default of http://localhost:3000 fails every synthesis request in production while the app looks perfectly healthy.
No .env
Nothing Léigh Dom reads at run time is secret, so it is not in user_apps_with_env and has no Vaultwarden note. If that changes, add it there and create a leigh-dom .env note rather than templating the value into git — the convention abair_website, geabaire-api, giob-geab and mao-teacher-portal follow.
Persistence
The stack declares a named data volume rather than a bind mount, so there is nothing to rsync in on first deploy: Docker creates it on the first up -d and the entrypoint creates the subtrees. pull && up -d leaves it intact; only down -v destroys it.
A named volume is not picked up by anything backing up /opt/apps. Treat the contents as regenerable — a lost volume costs users their document history, not the system its configuration.
nginx
networking/nginx-config/leighdom.abair.ie needs two things the standard template does not have:
client_max_body_size 32M. The app's cap is 25 MiB; nginx's 1 MB default would reject every upload with a bare 413 first. 32 MiB sits just above the app's cap, so an oversized file gets the app's error instead.proxy_http_version 1.1withConnectioncleared. The progress stream is Server-Sent Events, and nginx defaults to HTTP/1.0 upstream, which drops the keep-alive it relies on.
No buffering or timeout overrides are needed — the app sends X-Accel-Buffering: no and its own 15-second keep-alive.
Checking what is deployed
/api/health answers more than liveness, and 503 when the database or a statvfs call fails:
curl -s https://leighdom.abair.ie/api/health | jq
{
"ok": true,
"db": "ok",
"diskFreeBytes": 8123456789,
"runner": { "running": true, "activeJobs": 0 },
"video": { "available": true },
"queue": { "oldestQueuedAgeS": 0 }
}
runner and queue separate "healthy and idle" from "healthy-looking but backing up": oldestQueuedAgeS climbing while runner.running is false means the worker died without taking the process down. video.available: false means ffmpeg is missing — the client hides the export rather than offering a button that always fails, so check the probe, not the UI.
On the host:
ssh services-user-vm "cd /opt/apps/leigh-dom && docker compose ps && docker compose logs --tail 50"
Adding it to a fresh estate
ansible/hetzner-main/services-user-apps/leigh-dom/docker-compose.yml— the stack.leigh-domin theuser_appslist insetup-services-user.yml.networking/nginx-config/leighdom.abair.ie— the vhost.- Deploy:
make deploy-services-user-apps
make update-webserver - A Cloudflare DNS record for
leighdom. The wildcard*.abair.iecertificate already covers it. - Apply
supabase/0001_lei_uploads.sqlonce against the shared auth project. Idempotent, and the app degrades gracefully without it.
No allow-list entry is needed in the auth app: TRUSTED_BASE_DOMAINS = ["abair.ie"] in src/config/authOrigins.js already trusts every https://*.abair.ie origin. See Authentication.
Last updated 2026-09-16