This runbook covers v1 single-VPS Docker Compose operation for PostgreSQL and S3-compatible object storage. RabbitMQ queue contents are operational state and are not part of the durable business backup; replay metadata, parse jobs, parser results, requests, audit patches, and aggregate state live in PostgreSQL, while replay files and attachments live in object storage.
- PostgreSQL database:
solid_statsin thepostgresservice. - S3-compatible bucket:
${S3_BUCKET}in theminioservice. - Environment file:
.env.production, stored separately from git and protected as a secret. - Docker volumes:
postgres-dataandminio-data.
Create a timestamped backup directory on the VPS:
backup_id="$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "backups/${backup_id}/postgres" "backups/${backup_id}/objects"Dump PostgreSQL from the production Compose project:
docker compose --env-file .env.production -f docker-compose.prod.yml exec -T postgres \
pg_dump --format=custom --no-owner --no-privileges \
--username=solid --dbname=solid_stats \
> "backups/${backup_id}/postgres/solid_stats.dump"Mirror object storage with the MinIO client:
docker compose --env-file .env.production -f docker-compose.prod.yml run --rm \
--entrypoint /bin/sh \
-v "$PWD/backups/${backup_id}/objects:/backup" \
minio-create-bucket \
-c "mc alias set local http://minio:9000 solid \"${MINIO_ROOT_PASSWORD}\" && mc mirror local/${S3_BUCKET:-solid-replays} /backup"Store backups off-host after creation. Keep at least 7 daily backups and 4 weekly backups for v1 unless production storage constraints require a stricter policy.
Restore only into an isolated drill environment first.
-
Stop the API so no writers are active:
docker compose --env-file .env.production -f docker-compose.prod.yml stop api migrate
-
Restore PostgreSQL into an empty
solid_statsdatabase:docker compose --env-file .env.production -f docker-compose.prod.yml exec -T postgres \ dropdb --if-exists --username=solid solid_stats docker compose --env-file .env.production -f docker-compose.prod.yml exec -T postgres \ createdb --username=solid solid_stats docker compose --env-file .env.production -f docker-compose.prod.yml exec -T postgres \ pg_restore --clean --if-exists --no-owner --no-privileges \ --username=solid --dbname=solid_stats \ < "backups/${backup_id}/postgres/solid_stats.dump"
-
Restore object storage:
docker compose --env-file .env.production -f docker-compose.prod.yml run --rm \ --entrypoint /bin/sh \ -v "$PWD/backups/${backup_id}/objects:/restore/objects:ro" \ minio-create-bucket \ -c "mc alias set local http://minio:9000 solid \"${MINIO_ROOT_PASSWORD}\" && mc mirror --overwrite /restore/objects local/${S3_BUCKET:-solid-replays}"
-
Start services and validate:
docker compose --env-file .env.production -f docker-compose.prod.yml up -d curl -fsS http://127.0.0.1:3000/ready pnpm run openapi:check
pg_restore --list backups/${backup_id}/postgres/solid_stats.dumpsucceeds.- Restored API returns
GET /readywithstatus: "ready". GET /operations/parse-jobsreturns JSON.- MinIO bucket contains replay/object prefixes expected by the restored database.
- A restore drill result is recorded with backup id, backup age, operator, and validation outcome.
pnpm run ops:backup:check validates that this runbook still mentions the production Compose file, PostgreSQL dump/restore commands, MinIO mirror commands, production volumes, and the validation checklist.