Skip to content

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.edge

Apply 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 changes

Common 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.shpg_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: prod

Schedule 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>&1

Restore (overwrites):

bash
gunzip -c backups/<env>/<file>.sql.gz | docker exec -i redline-<env>-postgres-1 psql -U redline -d postgres

Local 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 pgadmin

Tunnel 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-orphans and stops it — re-run the up -d pgadmin command 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.

  1. Create a provider account (e.g. Brevo, EU, 300/day free) and authenticate amrkt.ch — add the SPF include, DKIM (brevo._domainkey.amrkt.ch) and DMARC (_dmarc.amrkt.ch) DNS records it shows. Generate an SMTP key.
  2. Fill the SMTP_* block in .env.<env> on the VPS:
    env
    SMTP_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
  3. Apply it (idempotent; reads .env.<env>, no restart):
    bash
    cd /opt/redline
    bash keycloak/configure-smtp.sh staging   # or: prod
  4. 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 (redline-account-admin, holding only the manage-users role). Provision it once per environment on the VPS, do staging first, then prod:

bash
cd /opt/redline
bash keycloak/provision-account-admin.sh staging   # then: prod
# paste the printed KEYCLOAK_ADMIN_CLIENT_ID/_SECRET into .env.<env>
./deploy/deploy.sh staging                          # then: prod

Step by step, per environment:

  1. provision-account-admin.sh <env> reads KEYCLOAK_HOSTNAME + the admin credentials from .env.<env>, idempotently creates the service-account client, and prints KEYCLOAK_ADMIN_CLIENT_ID + KEYCLOAK_ADMIN_CLIENT_SECRET.
  2. Paste those two values into .env.<env>.
  3. ./deploy/deploy.sh <env> recreates the mcp-server so it picks up the new credentials.

Until configured, erasure still wipes all app data and just leaves the Keycloak login for an operator to remove (the server falls back to a no-op user-admin). 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/healthz

Then 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.

A-Market — AI-first marketplace for cars, motorcycles and scooters.