It ûntwerp stiet yn docs/architecture/23-ops.md seksje 8. Dit is de runbook foar it
Docker Compose-produkt. De hantlieding is skreaun om folge te wurden troch ien dy’t him
net skreaun hat; as in stap ûndúdlik is, is dat in mankemint yn dit dokumint.
Wat beskerme wurdt en hoe
Asset
Hoe
Wêr
De database
WAL wurdt hieltyd argivearre, maksimaal elke 60 sekonden, fan de earste boot ôf
pgwal-volume
De database
Base backups mei pg_basebackup, standert deistich (backup-scheduler)
pgbackup-volume
De database
Fersifere kopyen fan base backups en WAL, elke fiif minuten (backup-offsite)
In aparte store dy’tst sels neamst
Bestannen
It files-volume. Kopiearje mei it reservekopy-ark fan dyn host of brûk ferzjonearre objektopslach
files-volume
Geheimen
docker/.env, benammen QUIRE_MASTER_KEY (en elke QUIRE_MASTER_KEY_RETIRED dy’t noch brûkt wurdt), QUIRE_BACKUP_ENCRYPTION_KEY en docker/secrets/audit-signing-key.pem
Bewarje in kopy bûten dizze host
Sykyndeksen, caches en renditions
Gjin reservekopy; wurde opnij makke
Doelen: in herstelpunt binnen 60 sekonden fan de fal ôf en in restore binnen 60 minuten foar
in database fan 500 GB.
Twa flaters komme faak foar. In database dy’t weromset is sûnder de bestannen, jout brutsen
siden. In database dy’t weromset is sûnder QUIRE_MASTER_KEY, kin de SSO-, webhook- en
yntegraasjebewizen yn de database net ûntsiferje; oant in master-keyrotaasje klear is sûnder
net-oploste wearden (key-rotation.md), jildt dit ek foar eardere kaaien.
Beide hearre by de reservekopy.
Reservekopyen meitsje
In base backup fan it hiele cluster:
docker compose -f docker/compose.yaml --profile backup run --rm backup
It behâldt de nijste QUIRE_BACKUP_KEEP base backups (standert 5) en snoeit WAL dy’t de
âldste net mear nedich hat, sadat it argyf net ûnbeheind groeie kin. Plan it deistich mei
cron of in systemd-timer op de host:
15 2 * * * cd /srv/quire && docker compose -f docker/compose.yaml --profile backup run --rm backup >> /var/log/quire-backup.log 2>&1
Of lit de stack it planne: it backup-profile draait backup-scheduler, dat elke
QUIRE_BACKUP_INTERVAL_HOURS oeren in base backup makket (standert 24), en ek
backup-offsite, hjirnei beskreaun.
docker compose -f docker/compose.yaml --profile backup up -d
Fersifere kopyen bûten de host
Beide volumes steane op deselde host as de database; in reservekopy op in mislearre masine
is gjin reservekopy. backup-offsite kopiearret elke base backup en elk argivearre WAL-segmint
fia de storage-port fersifere nei in aparte store en hâldt se dêr neffens de bewaartermyn:
Fersifering. AES-256-GCM mei QUIRE_BACKUP_ENCRYPTION_KEY (of it bestân neamd troch
QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 byte fan openssl rand -hex 32. Elk bestân hat
in eigen nonce en autentikaasjetag; sûnder kaai is de kopy net te lêzen en elke feroaring
wurdt ûntdutsen. Bewarje de kaai tegearre mei QUIRE_MASTER_KEY, fier fan dizze host en
de reservekopy-store. Sûnder de kaai is der gjin restore.
Wêr.QUIRE_BACKUP_STORAGE_DRIVER is s3, azure of local (in mounted remote disk
op QUIRE_BACKUP_STORAGE_ROOT). Dit binne ynstellingen fan bestânsopslach mei prefix
QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET,
QUIRE_BACKUP_S3_ACCESS_KEY_ID ensafuorthinne. Brûk in oare bucket en leafst in oar
akkount as foar de bestannen, mei bewiis dat skriuwe mar net wiskje kin as de provider dat tastiet.
Bewartermyn. De nijste QUIRE_BACKUP_OFFSITE_KEEP base backups (standert
QUIRE_BACKUP_KEEP, oars 7) en de WAL dy’t de âldste dêrfan nedich hat; âldere sets
en segminten wurde út de store wiske.
Wannear. Elke QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (standert 300). It ferstjoeren
is idempotint: al opsleine gegevens wurde oerslein, en in base backup telt pas as opslein
neidat syn manifest as lêste skreaun is.
Deselde kommando’s kinne mei de hân rinne:
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts shipdocker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts verify
Om op in nije host te herstellen, helje earst in set werom; folgje dan de stappen hjirûnder
mei de ophelle map ynstee fan it pgbackup-volume en it ophelle wal-archive ynstee fan pgwal:
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
Herstellen nei in tiidstip
Brûk dit nei gegevensferlies: in ferkearde ymport, in wiske kursus of in contractmigration
dy’t weromdraaid wurde moat. It ferfangt de live database; oefenje dêrom earst mei de drill hjirûnder.
Kies it doelsstip, yn UTC, krekt foar de skea:
2026-09-24 09:30:00+00. It auditlogboek (/admin/audit) lit dat momint meastal sjen.
Stopje alles dat skriuwt:
docker compose -f docker/compose.yaml stop web content worker scheduler collab
Hâld it skansearre cluster oant de restore kontrolearre is:
docker compose -f docker/compose.yaml stop postgresdocker run --rm -v quire_postgres18-data:/from -v quire_postgres-damaged:/to alpine cp -a /from/. /to/
Pak de nijste base backup foar it doelsstip út yn it datavolume en freegje herstel
nei it opjûne doel oan:
docker run --rm -v quire_pgbackup:/backups:ro -v quire_postgres18-data:/var/lib/postgresql postgres:18-alpine sh -euc ' base="$(ls -1d /backups/base-* | sort | tail -n 1)" # or the one before the target rm -rf /var/lib/postgresql/18/docker && mkdir -p /var/lib/postgresql/18/docker tar -xzf "$base/base.tar.gz" -C /var/lib/postgresql/18/docker touch /var/lib/postgresql/18/docker/recovery.signal chown -R postgres:postgres /var/lib/postgresql/18/docker && chmod 700 /var/lib/postgresql/18/docker'
Herstelle: start Postgres ien kear mei de herstelynstellingen út in Compose-override,
sadat it gewoane bestân net feroaret:
De override ferfangt it hiele kommando, dus werhellet de twa ynstellingen dêr’t herstel
fan ôfhinget: max_connections mei net leger wêze as op de primêre database (oars brekt
herstel ôf mei “insufficient parameter settings”) en it mountte pg_hba.conf.
docker compose -f docker/compose.yaml -f docker/compose.recover.yaml up -d postgresdocker compose -f docker/compose.yaml logs -f postgres # wait for "database system is ready"
Kontrolearje it foardatst immen talittest: de auditketen
(docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts)
en oft de ferlerne gegevens werom binne.
Gean werom nei normaal: docker compose -f docker/compose.yaml up -d. Dit start Postgres
opnij mei archiving oan; in nije WAL-tiidline begjint. Nim fuort in nije base backup.
De projektnamme quire stiet foar elke volumenaam; docker volume ls lit de eksakte nammen sjen.
Plande ferifikaasjedrill
backup-offsite docht ek elke QUIRE_BACKUP_DRILL_INTERVAL_HOURS in drill (standert 168,
wykliks), en op de folgjende trochrin nei in mislearre drill nochris. It hellet de nijste
base backup bûten de host op mei elk WAL-segmint dêrnei; ûntsiferet elk (wat bewiist dat de
kaai it noch iepenet en neat feroare is), ferliket elk bestân mei it manifest, kontrolearret
dat it argyf in Postgres-datamap is en dat der gjin gat sit yn WAL fanôf de backup. It rapport
wurdt as reports/drill-<time>.json yn de store en yn it logboek fan de tsjinst skreaun;
by mislearring neamt de drill it bestân of it earste ûntbrekkende segmint.
De restore drill
In reservekopy dy’t nea weromset is, is gjin reservekopy. De drill set de nijste folsleine
base backup fan foar it doelsstip plus it WAL-argyf werom yn in tydlike Postgres dy’t neat
mei de live database dielt, en bewiist it resultaat:
docker/scripts/restore-drill.sh # to ninety minutes agodocker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
It doelsstip is UTC yn krekt dy foarm. Der moat in âldere base backup wêze en argivearre
WAL dêrnei: nim by in nije ynstallaasje in base backup en wachtsje op it folgjende argivearre
segmint (maksimaal in minút by writes) foardatst in doelsstip nei de backup kiest. De drill
hat allinnich Docker en bash op de host nedich.
Elke stap lit de drill mislearje:
Werom te heljen: it tydlike cluster spilet op nei it doelsstip en iepenet.
Folslein: tal rigen fan elke tabel tsjin de live database (tooling/restore-drill).
De live database is sûnt it doelsstip fierder gien, dus kin it tal rigen ferskille mei
de grutste fan 500 rigen of in tsiende fan de tabelgrutte, yn beide rjochtingen (writes
meitsje it leger; by wiskjen hat de restore mear). In partition oanmakke nei it doelsstip
is gjin ûntbrekkende tabel. Ferbreedzje de romte op in drokkere ynstallaasje mei
QUIRE_DRILL_MAX_BEHIND en QUIRE_DRILL_MAX_DRIFT_RATIO. In ûntbrekkende of lege tabel
lit de test mislearje.
Yntakt: de audit-hashketen wurdt kontrolearre op de weromsette kopy.
Brûkber: de applikaasjerol kin lêze mei row-level security.
Tiid: fan start oant grien, neffens QUIRE_DRILL_RTO_SECONDS (standert 3600).
It skriuwt nea nei de live database of syn volumes: de reservekopy- en WAL-volumes wurde
allinnich-lêzen mount en it tydlike cluster wurdt oan de ein fuorthelle, oft de drill slagget
of net.
Set QUIRE_DRILL_REPORT op in paad om in JSON-rapport (sukses of mislearring) te skriuwen
en plan it út de Docker-host wei:
Doch dit alle moannen en foar elke upgrade. In mislearre drill hâldt in upgrade tsjin.
Ien kear it fearnsjier litst ien dy’t dizze runbook net skreaun hat in echte point-in-time-
restore dwaan op in ekstra host, mei allinnich dit dokumint.
Bestannen
Lokale bestannen steane yn it files-volume. Meitsje der tagelyk mei de database in
reservekopy fan en set beide tegearre werom:
docker run --rm -v quire_files:/files:ro -v "$PWD":/out alpine tar -czf /out/files-$(date -u +%Y%m%d).tar.gz -C /files .
Skeakelje by objektopslach bucketferzjes yn en bewarje net-aktuele ferzjes 35 dagen;
point-in-time-restore fan bestannen wurdt dan troch de bucket sels fersoarge.