Ang disenyo ay nasa docs/architecture/23-ops.md seksiyon 8. Ito ang runbook
para sa Docker Compose na produkto. Isinulat ito upang masundan ng taong
hindi sumulat nito; kung malabo ang isang hakbang, depekto iyon sa dokumentong ito.
Ano ang pinoprotektahan, at paano
Asset
Paano
Saan
Database
Tuloy-tuloy na ina-archive ang WAL, hindi hihigit sa bawat 60 segundo, mula sa unang boot
pgwal volume
Database
Mga base backup gamit ang pg_basebackup, araw-araw bilang default (backup-scheduler)
pgbackup volume
Database
Mga naka-encrypt na kopya ng mga base backup at WAL, bawat limang minuto (backup-offsite)
Hiwalay na store na ikaw ang magtatalaga
Mga file
Ang files volume. Kopyahin ito gamit ang backup tool ng host mo, o gumamit ng versioned object storage
files volume
Mga lihim
docker/.env, higit sa lahat ang QUIRE_MASTER_KEY (at anumang QUIRE_MASTER_KEY_RETIRED na ginagamit pa), QUIRE_BACKUP_ENCRYPTION_KEY, at docker/secrets/audit-signing-key.pem
Magtabi ng kopya sa labas ng host na ito
Mga search index, cache, rendition
Hindi bina-backup; muling ginagawa
Mga target: recovery point na hindi lalampas sa 60 segundo bago ang pagkabigo,
at restore sa loob ng 60 minuto para sa 500 GB na database.
Dalawang pagkakamali ang karaniwan. Ang database na na-restore nang wala ang mga file ay nagdudulot
ng sirang mga pahina. Ang database na na-restore nang wala ang QUIRE_MASTER_KEY ay hindi
makakapag-decrypt ng mga kredensiyal sa SSO, webhook at integration na laman nito; hanggang sa
matapos ang master key rotation nang walang natitirang hindi nalutas
(key-rotation.md), kasama rito ang mga retiradong key. Bahagi ng backup ang dalawa.
Pagkuha ng mga backup
Base backup ng buong cluster:
docker compose -f docker/compose.yaml --profile backup run --rm backup
Pinananatili nito ang pinakabagong QUIRE_BACKUP_KEEP na mga base backup (default 5) at pinuputol
ang WAL na hindi na kailangan ng pinakaluma, kaya hindi lalaki nang walang hanggan ang archive.
I-iskedyul ito araw-araw gamit ang cron o systemd timer sa host:
15 2 * * * cd /srv/quire && docker compose -f docker/compose.yaml --profile backup run --rm backup >> /var/log/quire-backup.log 2>&1
O hayaang ang stack ang mag-iskedyul nito: pinapatakbo ng backup profile ang backup-scheduler,
na kumukuha ng base backup bawat QUIRE_BACKUP_INTERVAL_HOURS (default 24),
at ang backup-offsite na inilalarawan sa susunod.
docker compose -f docker/compose.yaml --profile backup up -d
Mga naka-encrypt na kopyang nasa labas ng host
Nasa parehong host ng database ang dalawang volume, at hindi maituturing na backup
ang backup na nasa makinang pumalya. Kinokopya ng backup-offsite ang bawat base backup
at bawat naka-archive na WAL segment papunta sa hiwalay na store sa pamamagitan ng storage port,
ini-encrypt ito, at pinananatili roon ayon sa retention:
Encryption. AES-256-GCM gamit ang QUIRE_BACKUP_ENCRYPTION_KEY (o ang file
na pinangalanan ng QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 byte mula sa
openssl rand -hex 32. May sariling nonce at authentication tag ang bawat file,
kaya hindi mababasa ang kopya kung wala ang key at natutukoy ang anumang pagbabago rito.
Itabi ang key kasama ng QUIRE_MASTER_KEY, malayo sa host na ito at sa backup store.
Kung wala ang key, walang maibabalik.
Saan.QUIRE_BACKUP_STORAGE_DRIVER ay s3, azure o local (isang naka-mount
na remote disk sa QUIRE_BACKUP_STORAGE_ROOT). Mga setting ito ng file storage na may
QUIRE_BACKUP_ na prefix: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET,
QUIRE_BACKUP_S3_ACCESS_KEY_ID, at iba pa. Gumamit ng ibang bucket at, kung maaari,
ibang account kaysa sa mga file, na may mga kredensiyal na puwedeng magsulat pero hindi
magtanggal kung pinapayagan ito ng provider.
Retention. Pinakabagong QUIRE_BACKUP_OFFSITE_KEEP na mga base backup (default
QUIRE_BACKUP_KEEP, o 7 kung wala ito) at ang WAL na kailangan ng pinakaluma sa mga ito;
tatanggalin sa store ang mas lumang mga set at segment.
Kailan. Bawat QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (default 300). Idempotent ang pagpapadala:
nilalaktawan ang naka-store na, at maituturing lang na naka-store ang base backup kapag naisulat
na ang manifest nito, bilang huling hakbang.
Manu-mano ring pinapatakbo ang parehong command:
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
Para mag-restore sa bagong host, kunin muna roon ang isang set, saka sundin ang mga hakbang sa ibaba
gamit ang nakuha mong directory kapalit ng pgbackup volume at ang nakuha mong wal-archive
kapalit ng pgwal:
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
Pag-restore sa isang tiyak na oras
Gamitin ito pagkatapos mawalan ng data: maling import, naburang kurso, o paglipat ng kontrata
na kailangan mong bawiin. Pinapalitan nito ang live na database, kaya magsanay muna rito
gamit ang drill sa ibaba.
Piliin ang target na oras, sa UTC, bago mismo ang pinsala:
2026-09-24 09:30:00+00. Karaniwang ipinapakita ng audit log (/admin/audit) ang
sandaling iyon.
Ihinto ang lahat ng sumusulat:
docker compose -f docker/compose.yaml stop web content worker scheduler collab
Panatilihin ang napinsalang cluster hanggang ma-verify ang restore:
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/
I-unpack sa data volume ang pinakabagong base backup na mas luma sa target, at humiling ng
recovery sa tinukoy na oras:
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'
Mag-recover: minsanang simulan ang Postgres gamit ang mga setting ng recovery mula sa
Compose override para manatiling hindi nagbabago ang karaniwang file:
Pinapalitan ng override ang buong command, kaya inuulit nito ang dalawang setting na kailangan
ng recovery: max_connections na hindi mas mababa sa primary (kung hindi, hihinto ang recovery
dahil sa “insufficient parameter settings”) at ang naka-mount na 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"
Suriin ito bago papasukin ang sinuman: ang audit chain
(docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts),
at tiyaking naibalik ang nawalang data.
Bumalik sa normal: docker compose -f docker/compose.yaml up -d. Magsisimulang muli ang
Postgres na naka-on ang archiving, at magsisimula ang bagong WAL timeline. Kumuha agad ng bagong
base backup.
Nilalagyan ng pangalan ng proyekto na quire ang bawat volume; ipinapakita ng docker volume ls
ang eksaktong mga pangalan.
Naka-iskedyul na verification drill
Nagpapatakbo rin ng drill ang backup-offsite bawat QUIRE_BACKUP_DRILL_INTERVAL_HOURS
(default 168, lingguhan), at muli sa susunod na pagtakbo kapag pumalya ang isa. Kinukuha nito
ang pinakabagong off-host base backup at bawat WAL segment pagkatapos nito, dine-decrypt ang bawat
isa (na nagpapatunay na nabubuksan pa rin ng key ang mga ito at walang binago), inihahambing ang
bawat file sa manifest nito, sinusuri kung Postgres data directory ang archive, at tinitiyak na
walang puwang sa WAL mula sa backup pataas. Isinusulat ang ulat sa store bilang
reports/drill-<time>.json at sa log ng serbisyo; tinutukoy ng pumalyang drill ang file o ang
unang nawawalang segment.
Ang restore drill
Hindi maituturing na backup ang backup na hindi pa na-restore. Ibinabalik ng drill ang
pinakabagong kumpletong base backup na kinuha bago ang target, kasama ang WAL archive,
sa isang pansamantalang Postgres na walang anumang ibinabahagi sa live na database, at
pinatutunayan ang resulta:
docker/scripts/restore-drill.sh # to ninety minutes agodocker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
UTC ang target at dapat eksaktong ganito ang format. Kailangan nito ng base backup na mas luma rito
at naka-archive na WAL na lampas dito: sa bagong install, kumuha ng base backup at hintayin ang
susunod na naka-archive na segment (hindi hihigit sa isang minuto kapag may mga write) bago pumili
ng target na pagkatapos ng backup. Docker at bash lang sa host ang kailangan ng drill.
Ang bawat hakbang na ito ay nagpapabagsak sa drill:
Kakayahang mag-recover: nare-replay ng pansamantalang cluster ang mga log hanggang sa target at nagbubukas ito.
Pagiging kumpleto: ihambing ang bilang ng row ng bawat talahanayan sa live na database
(tooling/restore-drill). Nagpatuloy na ang pagbabago sa live na database mula sa target,
kaya maaaring magkaiba ang isang talahanayan nang hanggang sa mas malaki sa 500 row o ikasampu
ng laki nito, sa alinmang direksiyon (napag-iiwanan ito ng mga write, samantalang mas marami
ang nananatili sa restore kapag may mga deletion); hindi nawawalang talahanayan ang partition
na ginawa pagkatapos ng target. Palawakin ang allowance sa mas abalang install gamit ang
QUIRE_DRILL_MAX_BEHIND at QUIRE_DRILL_MAX_DRIFT_RATIO. Nabibigo ito kapag may nawawala o
naubos na talahanayan.
Integridad: nabe-verify ang audit hash chain sa na-restore na kopya.
Pagiging magagamit: nakakabasa ang application role gamit ang row-level security.
Oras: mula simula hanggang maging berde, ayon sa QUIRE_DRILL_RTO_SECONDS
(default 3600).
Hindi ito kailanman sumusulat sa live na database o mga volume nito: read-only ang pagkaka-mount
ng mga backup at WAL volume at inaalis ang pansamantalang cluster sa katapusan, pumasa man o bumagsak.
Itakda ang QUIRE_DRILL_REPORT sa isang path upang maisulat doon ang JSON report, pumasa man o
bumagsak, at patakbuhin ito ayon sa iskedyul mula sa Docker host:
Patakbuhin ito buwan-buwan at bago ang bawat upgrade. Pinipigilan ng pumalyang drill ang upgrade.
Kada quarter, magpatest ng tunay na point-in-time restore sa ekstrang host sa taong hindi sumulat
ng runbook na ito, gamit lamang ang dokumentong ito.
Mga file
Nasa files volume ang mga lokal na file. I-backup ito kasabay ng database, sa parehong oras,
at i-restore ang dalawa nang magkasama:
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 .
Sa object storage, i-on ang bucket versioning at panatilihin nang 35 araw ang mga hindi na
pinakabagong bersiyon; ang bucket mismo ang bahala sa point-in-time recovery ng mga file.