Developer DocsDeployment

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

ServiceSubdomain (reference)Container(s)
Operator dashboardcrm.logiqdesk.comweb (Next.js, :3000)
Player portalplay.logiqdesk.complayer (Next.js, :3001)
REST API v1crm-api.logiqdesk.comapi (Next.js, :3002)
Documentationcrm-docs.logiqdesk.comdocs (Nextra, :3003)
Supabase Studio + gatewaycrm-db.logiqdesk.comkongstudio / auth / rest / realtime / storage
Databaseinternaldb (Supabase Postgres)
Cache / queueinternalredis
Background workerinternalworker (BullMQ)
Reverse proxy + HTTPS:80 / :443caddy

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_URL points at our own gateway.
  • Data ownership — relevant for iGaming compliance; append-only balance_transactions live 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 docs

Wait 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 the postgres role. New ones are written idempotent, so re-applying is safe.
  • infra/db-migrate.sh applies 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.