Appearance
Operations
Day-to-day running of the live VPS: SSH access, editing per-environment config, database backups, the pgAdmin console, Keycloak email, and testing remote connectors locally. The deploy pipeline itself is in Deploy.
The examples use the deploy host and key from your GitHub Environments (VPS_HOST, VPS_SSH_KEY). Substitute your own; <vps> is the server IP/hostname.
SSH access & editing env vars
Secrets and per-environment config live in .env.<env> files on the server (never in git).
bash
# Windows (PowerShell)
ssh -i $env:USERPROFILE\.ssh\redline deploy@<vps>
# Linux / macOS
ssh -i ~/.ssh/redline deploy@<vps>
cd /opt/redline
nano .env.staging # or .env.prod / .env.edgeApply changes by recreating the stack (pulls fresh images, recreates containers):
bash
./deploy/deploy.sh staging # or: prod — app/env changes
./deploy/deploy.sh edge # Caddy / domain changesCommon edits: SMTP_*, SENTRY_DSN, OAUTH_AUDIENCE, image tags, and (for the docs site) DOCS_IMAGE / DOCS_DOMAIN.
Database backups
Rotated local backups via deploy/backup-db.sh — pg_dumpall of the whole cluster (the redline and keycloak databases plus roles), gzipped into backups/<env>/, keeping 14 days:
bash
cd /opt/redline
bash deploy/backup-db.sh staging # or: prodSchedule it daily (deploy user's crontab):
cron
30 3 * * * cd /opt/redline && bash deploy/backup-db.sh staging >> /opt/redline/backups/backup.log 2>&1
45 3 * * * cd /opt/redline && bash deploy/backup-db.sh prod >> /opt/redline/backups/backup.log 2>&1Restore (overwrites):
bash
gunzip -c backups/<env>/<file>.sql.gz | docker exec -i redline-<env>-postgres-1 psql -U redline -d postgresLocal rotation guards against corruption / bad migrations / accidental deletes — not host loss. For off-box safety, add
rclone copy backups/<env> remote:redline-<env>to the cron.
pgAdmin (private DB console)
pgAdmin is opt-in and loopback-only (never internet-exposed), reached over an SSH tunnel. First create the least-privilege read-only role and set PGADMIN_* in .env.<env>, then start it on demand:
bash
cd /opt/redline
bash deploy/create-readonly-db-role.sh staging # creates pgadmin_ro
docker compose --env-file .env.staging \
-f docker-compose.yml -f deploy/docker-compose.prod.yml -f deploy/docker-compose.admin.yml \
up -d pgadminTunnel in and browse:
bash
# local machine — keep this session open
ssh -i ~/.ssh/redline -L 5050:localhost:5050 deploy@<vps>
# then open http://localhost:5050 (login: admin@amrkt.ch + PGADMIN password from .env)In pgAdmin, register a server → host postgres, port 5432, db redline, user pgadmin_ro. Handy checks:
sql
select status, count(*) from vehicles group by status;
select * from audit_log order by created_at desc limit 20;pgAdmin is on-demand: a later
./deploy/deploy.sh <env>runs--remove-orphansand stops it — re-run theup -d pgadmincommand when you next need it. Keeping it down is the safer default.
Keycloak email (Brevo SMTP)
"Forgot password" and email verification need outbound SMTP. Hetzner blocks port 25, so use a transactional provider's SMTP relay (port 587). The realm export already sets resetPasswordAllowed/verifyEmail; only the credentials are applied at runtime.
- Create a provider account (e.g. Brevo, EU, 300/day free) and authenticate
amrkt.ch— add the SPFinclude, DKIM (brevo._domainkey.amrkt.ch) and DMARC (_dmarc.amrkt.ch) DNS records it shows. Generate an SMTP key. - Fill the
SMTP_*block in.env.<env>on the VPS:envSMTP_HOST=smtp-relay.brevo.com SMTP_PORT=587 SMTP_STARTTLS=true SMTP_USER=<brevo-login> SMTP_PASSWORD=<brevo-key> SMTP_FROM=no-reply@amrkt.ch - Apply it (idempotent; reads
.env.<env>, no restart):bashcd /opt/redline bash keycloak/configure-smtp.sh staging # or: prod - Test: trigger "Forgot password" for an inbox you control.
Enable Keycloak login deletion on account erasure
DELETE /v1/me/account always erases the user's app data; to also delete their Keycloak login the server needs a least-privilege service-account client. Provision it once per realm:
- On the VPS, with
.env.<env>present (it readsKEYCLOAK_HOSTNAME+ the admin credentials):bashIt idempotently creates thecd /opt/redline bash keycloak/provision-account-admin.sh staging # or: prodredline-account-adminclient (service account,manage-usersonly) and printsKEYCLOAK_ADMIN_CLIENT_ID+KEYCLOAK_ADMIN_CLIENT_SECRET. - Paste those two values into
.env.<env>, then redeploy so the mcp-server picks them up:bash./deploy/deploy.sh staging # or: prod
Until configured, erasure still wipes all app data and just leaves the Keycloak login for an operator to remove. See Account deletion.
Test a remote connector locally (ngrok)
To exercise the full Claude/ChatGPT OAuth connector flow without a VPS, tunnel the local stack over HTTPS with ngrok:
bash
cp ngrok.yml.example ngrok.yml
ngrok start --all --config ngrok.yml --config "$HOME/.config/ngrok/ngrok.yml"
cp .env.ngrok.example .env.ngrok
# set MCP_PUBLIC_URL = the MCP ngrok URL, KEYCLOAK_HOSTNAME = the Keycloak ngrok URL
docker compose -f docker-compose.yml -f docker-compose.ngrok.yml --env-file .env.ngrok up -d --build
curl https://YOUR-MCP.ngrok-free.app/healthzThen in Claude: Settings → Connectors → Add custom → https://YOUR-MCP.ngrok-free.app/mcp, log in as dealer / dealer, and approve the scopes. The realm ships with anonymous Dynamic Client Registration open and the listings:* scopes as realm defaults, so no manual Keycloak steps are needed. See MCP in ChatGPT / Claude for the connector UX.