Deployment
PlaylogiQ is self-hosted: the whole platform runs as one Docker Compose stack
on a single Hetzner server, behind Caddy for automatic HTTPS. It is part of the
LogiQDesk suite under logiqdesk.com. Everything lives in the infra/ directory.
What runs
| Service | Subdomain (reference) | Container(s) |
|---|---|---|
| Operator dashboard | crm.logiqdesk.com | web (Next.js, :3000) |
| Player portal | play.logiqdesk.com | player (Next.js, :3001) |
| REST API v1 | crm-api.logiqdesk.com | api (Next.js, :3002) |
| Documentation | crm-docs.logiqdesk.com | docs (Nextra, :3003) |
| Supabase Studio + gateway | crm-db.logiqdesk.com | kong → studio / auth / rest / realtime / storage |
| Database | internal | db (Supabase Postgres) |
| Cache / queue | internal | redis |
| Background worker | internal | worker (BullMQ) |
| Reverse proxy + HTTPS | :80 / :443 | caddy |
Everything is driven by a single infra/.env. Caddy issues and renews TLS
certificates automatically.
Why self-hosted
- Cost & predictability — one server, no per-event/per-MAU billing.
- No vendor lock-in — Supabase is self-hosted; only
NEXT_PUBLIC_SUPABASE_URLpoints at our own gateway. - Data ownership — relevant for iGaming compliance; append-only
balance_transactionslive on our server, backed up off-site daily.
Images & CI
GitHub Actions builds and pushes Docker images to GHCR on every push to main
(ghcr.io/<owner>/playlogiq-<app>:latest). The server pulls those images — it
does not build locally.
Applying updates
The typical update flow on the server (from infra/):
cd /opt/playlogiq/infra
git pull origin main # get new migrations + compose changes
# apply any new SQL migrations (idempotent; apply only the new ones)
docker compose exec -T db psql -v ON_ERROR_STOP=1 -U postgres -d postgres \
< ../supabase/migrations/NNN_description.sql
# reload PostgREST if a migration added a table/RPC it must expose
docker compose restart rest
# roll the app containers to the freshly built images
docker compose pull web api worker docs
docker compose up -d --force-recreate web api worker docsWait for the CI “Build & push images” workflow to go green for the commit before pulling, or you’ll roll to a stale image. Verify with
docker inspect playlogiq-web-1 --format '{{.Image}}'.
Migrations
- Migrations are sequential
supabase/migrations/NNN_*.sql, applied as thepostgresrole. New ones are written idempotent, so re-applying is safe. infra/db-migrate.shapplies the whole set (for a fresh database). For an already-migrated production database, apply only the new files individually (older migrations aren’t all idempotent).- After a schema change, reload PostgREST (
docker compose restart rest) if a new table or RPC must be exposed, and roll the apps that consume it. - Verify schema completeness after a batch — compare expected objects against
pg_tables/pg_proc(a migration was skipped in prod once). See Database & RLS.
Environment
Secrets live in infra/.env (Supabase keys, DB password, VAPID keys,
TELEGRAM_BOT_TOKEN, SMTP creds, REDIS_URL, …). The api container also
honours ALLOW_SERVICE_ROLE_API_KEY — leave it unset in production (the
service-role API-key backdoor stays off). See
PAM Integration → Authentication.
Backups
A daily job backs up the database off-site to a Hetzner Storage Box
(infra/backup/). Keep infra/.env backups out of world-readable locations.
Go-live hygiene
Before exposing the platform to third parties: rotate any API keys / secrets
shared during development, confirm .env holds production values, tighten the
server deploy key to read-only, and verify schema completeness in prod. The
project CLAUDE.md keeps the authoritative go-live checklist.