Operations
Health checks
curl -fsS https://account.example.com/healthz
{
"status": "ok",
"service": "universal-gaming-accounts",
"version": "1.4.0",
"database": "postgres"
}
The endpoint performs a database query. A failure returns a non-2xx status.
Docker health check:
docker compose ps
docker inspect --format '{{json .State.Health}}' \
"$(docker compose ps -q accounts)"
Logs
docker compose logs -f accounts
docker compose logs --tail=200 db
Request log format
[http] 200 GET /api/v1/leaderboards 12.4ms ip=203.0.113.10 peer=10.0.0.2 ua="UniversalGaming/2.9.0"
- Query strings are stripped.
- Bodies, tokens, passwords and e-mail addresses are never logged.
ipis the resolved client IP;peeris the direct socket peer.uais JSON-escaped and length-limited.
Failed logins
[auth] login failed ip=203.0.113.10
Only the IP is logged.
Database
[db] postgres connected
[db] migration 2 (password reset tokens and session versioning) applied
PostgreSQL also logs connections and disconnections because Compose starts it with log_connections=on and log_disconnections=on.
[mail] SMTP host=mail.example.com port=587 secure=false requireTLS=true
If you see SMTP_SECURE=true is not valid for STARTTLS port 587, the mode was auto-corrected. Fix .env to remove the warning.
Management panel
Enabled when MANAGEMENT_PASSWORD is non-empty:
https://account.example.com/management
- HTTP Basic auth.
- Username
MANAGEMENT_USER(defaultadmin). - Read-only: account, score and match counts plus recent activity.
Cache-Control: no-store.
Test it:
curl -u admin:"$(cat /root/.universal-gaming-management-password)" \
https://account.example.com/management | head
Keep the password in a root-only file (chmod 600) outside the repository.
SMTP verification
docker compose exec accounts node -e "
import('./src/config.js').then(async ({loadConfig}) => {
const {createMailer} = await import('./src/mailer.js');
const m = createMailer(loadConfig());
if (!m.enabled) throw new Error('SMTP is not configured');
await m.verify();
console.log('SMTP verify ok');
});
"
Then test a real flow:
- Register an account with an e-mail.
- Confirm the address from the mail.
- Request a password reset.
- Open the reset link and choose a new password.
- Confirm old sessions now return 401.
Backups
Automated pre-update backup
./update.sh writes:
backups/pre-update-YYYYMMDDTHHMMSSZ.dump
Scheduled backup
Run from cron or a systemd timer:
#!/usr/bin/env bash
set -euo pipefail
cd /srv/universal-gaming-backend
mkdir -p backups
out="backups/scheduled-$(date -u +%Y%m%dT%H%M%SZ).dump"
docker compose exec -T db \
sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > "$out"
chmod 600 "$out"
find backups -name 'scheduled-*.dump' -mtime +30 -delete
Copy dumps off-host. A backup on the same disk does not survive disk loss.
Verify a dump
pg_restore --list backups/pre-update-YYYYMMDDTHHMMSSZ.dump | head
Restore
docker compose stop accounts
docker compose exec -T db \
sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists' \
< backups/pre-update-YYYYMMDDTHHMMSSZ.dump
docker compose start accounts
curl -fsS http://127.0.0.1:6767/healthz
After restoring, verify:
- A known account can sign in.
- Leaderboard rows are present.
- Chess matches load.
schema_migrationscontains the expected versions.
Data durability
- Base Compose uses the named volume
postgres-data. - Production may use a bind mount through a gitignored
docker-compose.override.yml. - Never delete the data directory or volume. It contains all accounts, scores and matches.
- Changing
POSTGRES_PASSWORDdoes not change an existing role. UseALTER ROLE. - Keep the database and backups on a monitored disk.
Disk and images
df -h /
docker system df
docker image prune -f
Old application images accumulate after each release. Prune them after confirming the current deployment is healthy. Do not prune volumes.
Monitoring checklist
/healthzreturnsokand the expected version.docker compose psshows healthy services.- Request logs show real client IPs.
- Failed-login volume is normal.
- SMTP verify succeeds.
- Disk usage is below the alert threshold.
- Latest backup exists and is readable.
- Backups are copied off-host.
schema_migrationsmatches the release.- Management panel is reachable only as intended.
Graceful shutdown
The server handles SIGINT and SIGTERM:
- Stops accepting connections.
- Closes the database pool.
- Exits after at most 10 seconds.
Compose sets stop_grace_period: 15s and init: true.
Incident checklist
- Check
/healthz, container status and logs. - Do not delete the database volume.
- Take a fresh dump before changing anything.
- Roll back the image or restore the pre-update dump.
- Rotate
JWT_SECRETif session integrity is in doubt; this signs everyone out. - Rotate SMTP and management passwords if they may have leaked.
- Verify account access, leaderboard data and chess matches after recovery.
Universal Gaming Accounts
- Home
- Deployment
- Configuration
- API Reference
- Database and Migrations
- Security
- Operations
- Development and Testing
- Troubleshooting
Repositories
The accounts backend is optional. Universal Gaming remains fully playable offline without it.