El disseny es descriu a la secció 8 de docs/architecture/23-ops.md. Aquest
és el manual operatiu del producte Docker Compose. Està escrit perquè el pugui
seguir algú que no l’hagi redactat; si un pas no és clar, és un defecte
d’aquest document.
Què es protegeix i com
Recurs
Mètode
Ubicació
Base de dades
WAL arxivat contínuament, com a màxim cada 60 segons, des de la primera arrencada
Volum pgwal
Base de dades
Còpies base amb pg_basebackup, cada dia per defecte (backup-scheduler)
Volum pgbackup
Base de dades
Còpies xifrades de les còpies base i del WAL, cada cinc minuts (backup-offsite)
Magatzem separat que indiqueu
Fitxers
Volum files. Copieu-lo amb l’eina de còpia del vostre host o utilitzeu emmagatzematge d’objectes amb versions
Volum files
Secrets
docker/.env, sobretot QUIRE_MASTER_KEY (i qualsevol QUIRE_MASTER_KEY_RETIRED que encara s’utilitzi), QUIRE_BACKUP_ENCRYPTION_KEY i docker/secrets/audit-signing-key.pem
Conserveu-ne una còpia fora d’aquest host
Índexs de cerca, memòries cau i versions derivades
No se’n fa còpia; es tornen a crear
Objectius: recuperar-se fins a un punt situat a menys de 60 segons de l’error i
restaurar en menys de 60 minuts una base de dades de 500 GB.
Dos errors són habituals. Si restaureu la base de dades sense els fitxers,
les pàgines apareixen malmeses. Si la restaureu sense QUIRE_MASTER_KEY,
no podreu desxifrar les credencials SSO, webhook i integració que conté; fins
que una rotació de la clau mestra acabi sense res pendent
(key-rotation.md), també hi calen les claus retirades. Totes
aquestes dades formen part de la còpia de seguretat.
Fer còpies de seguretat
Per fer una còpia base de tot el clúster:
docker compose -f docker/compose.yaml --profile backup run --rm backup
Es conserven les QUIRE_BACKUP_KEEP còpies base més recents (5 per defecte) i
s’eliminen els arxius WAL que ja no necessita la còpia més antiga, perquè
l’arxiu no creixi sense límit. Programeu-ho cada dia amb cron o un temporitzador
systemd a l’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 deixeu que ho programi el stack: el perfil backup executa
backup-scheduler, que crea una còpia base cada QUIRE_BACKUP_INTERVAL_HOURS
(24 per defecte), i backup-offsite, que s’explica a continuació.
docker compose -f docker/compose.yaml --profile backup up -d
Còpies xifrades fora de l’host
Tots dos volums són al mateix host que la base de dades, i una còpia de
seguretat a la màquina que ha fallat no és cap còpia de seguretat. El servei
backup-offsite copia totes les còpies base i tots els segments WAL arxivats a
un magatzem separat a través del port d’emmagatzematge, els xifra i els conserva
segons la política de retenció:
Xifratge. AES-256-GCM amb QUIRE_BACKUP_ENCRYPTION_KEY (o el fitxer
indicat per QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 bytes generats amb
openssl rand -hex 32. Cada fitxer té un nonce i una etiqueta
d’autenticació propis, de manera que no es pot llegir cap còpia sense la clau
i qualsevol modificació es detecta. Conserveu la clau amb
QUIRE_MASTER_KEY, lluny d’aquest host i del magatzem de còpies. Sense la
clau no es pot restaurar.
Ubicació.QUIRE_BACKUP_STORAGE_DRIVER pot ser s3, azure o local
(disc remot muntat a QUIRE_BACKUP_STORAGE_ROOT). S’utilitzen les opcions
d’emmagatzematge de fitxers amb el prefix QUIRE_BACKUP_:
QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET,
QUIRE_BACKUP_S3_ACCESS_KEY_ID i altres. Feu servir un bucket diferent i,
idealment, un compte diferent del dels fitxers, amb credencials que puguin
escriure però no suprimir, si el proveïdor ho permet.
Retenció. Les còpies base més recents, QUIRE_BACKUP_OFFSITE_KEEP
(per defecte, QUIRE_BACKUP_KEEP o 7), i el WAL que necessita la més antiga;
els conjunts i segments anteriors se suprimeixen del magatzem.
Freqüència. Cada QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (300 per defecte).
L’enviament és idempotent: se salta el que ja hi ha desat i una còpia base
només es considera desada quan el seu manifest s’ha escrit al final.
Les ordres es poden executar manualment:
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
Per restaurar en un host nou, recupereu primer un conjunt i, després, seguiu
els passos següents fent servir el directori recuperat en lloc del volum
pgbackup i l’arxiu wal-archive recuperat en lloc de pgwal:
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
Restaurar a un moment concret
Feu-ho després d’una pèrdua de dades: una importació incorrecta, un curs
suprimit o una migració de retirada que cal desfer. Se substituirà la base de
dades activa; per tant, abans practiqueu el simulacre següent.
Trieu l’hora de destinació, en UTC, just abans del dany:
2026-09-24 09:30:00+00. L’hora sol aparèixer al registre d’auditoria
(/admin/audit).
Atureu tots els serveis que escriuen:
docker compose -f docker/compose.yaml stop web content worker scheduler collab.
Conserveu el clúster danyat fins que s’hagi comprovat la restauració:
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/
Descomprimiu la còpia base més recent anterior a l’hora de destinació
al volum de dades i demaneu una recuperació limitada:
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'
Recupereu les dades: inicieu Postgres una vegada amb les opcions de
recuperació mitjançant una configuració alternativa de Compose, per no
modificar el fitxer habitual:
La configuració alternativa substitueix l’ordre sencera; per tant, cal
repetir les dues opcions necessàries per a la recuperació: max_connections
no pot ser inferior al valor del servidor principal (si no, la recuperació
s’atura amb «insufficient parameter settings») i cal tornar a muntar
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"
Comproveu-ho abans de permetre l’accés: verifiqueu la cadena
d’auditoria (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) i que les dades perdudes s’hagin recuperat.
Torneu a l’operació habitual amb docker compose -f docker/compose.yaml up -d. Això torna a iniciar Postgres amb l’arxivament activat i comença una nova línia de temps del WAL. Feu immediatament una còpia base nova.
El nom de projecte quire és el prefix dels volums; docker volume ls mostra
els noms exactes.
Simulacre de verificació programat
backup-offsite també executa un simulacre cada QUIRE_BACKUP_DRILL_INTERVAL_HOURS
(168 hores, una vegada a la setmana per defecte) i, després d’un error, el
repeteix a la següent execució. Recupera del host extern la còpia base més
recent i tots els segments WAL posteriors, els desxifra (per comprovar que la
clau encara els pot obrir i que ningú no els ha modificat), compara cada fitxer
amb el seu manifest, comprova que l’arxiu és un directori de dades Postgres i
que no hi ha cap buit al WAL des de la còpia base. L’informe s’escriu al
magatzem com a reports/drill-<time>.json i al registre del servei; si el
simulacre falla, n’indica el fitxer o el primer segment absent.
Simulacre de restauració
Una còpia que no s’ha restaurat mai no és cap còpia. El simulacre restaura la
còpia base completa més recent anterior a l’hora de destinació i l’arxiu WAL en
un Postgres temporal, independent del servidor en ús, i en comprova el resultat:
docker/scripts/restore-drill.sh # to ninety minutes agodocker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
L’hora de destinació ha d’estar en UTC i tenir exactament aquest format. Cal
una còpia base anterior i segments WAL arxivats posteriors: en una instal·lació
nova, feu una còpia base i espereu que s’arxivi el segment següent (com a
màxim un minut si hi ha escriptures) abans de triar una hora posterior a la
còpia. El simulacre només necessita Docker i bash a l’host.
Cada pas següent pot fer fallar el simulacre:
Recuperabilitat: el clúster temporal reprodueix les dades fins a l’hora
de destinació i s’inicia.
Integritat: es comprova el nombre de files de cada taula en relació
amb la base activa (tooling/restore-drill). La base activa ha avançat des
de l’hora de destinació, així que cada taula pot diferir en el valor més
gran entre 500 files i una dècima part de la seva mida, en totes dues
direccions (les escriptures poden deixar-la endarrerida i les supressions
poden fer que la còpia restaurada en tingui més). Una partició creada
després de l’hora de destinació no compta com una taula perduda. En una
instal·lació amb més activitat, augmenteu el marge amb
QUIRE_DRILL_MAX_BEHIND i QUIRE_DRILL_MAX_DRIFT_RATIO. Si manca una
taula o és buida, el simulacre falla.
Integritat: es verifica la cadena hash d’auditoria de la còpia
restaurada.
Usabilitat: el rol d’aplicació pot llegir respectant la seguretat a
nivell de fila.
Durada: temps fins al resultat correcte en relació amb
QUIRE_DRILL_RTO_SECONDS (3600 per defecte).
No s’escriu mai a la base de dades en ús ni als seus volums: els volums de
còpia i WAL es munten només per a lectura, i el clúster temporal s’elimina al
final, tant si la prova s’aprova com si falla.
Establiu QUIRE_DRILL_REPORT amb el camí del fitxer per obtenir un informe
JSON, tant si s’aprova com si falla. Executeu periòdicament l’script des de
l’host Docker:
Executeu-lo cada mes i abans de cada actualització. Si falla, no es pot
actualitzar. Un cop cada trimestre, demaneu a algú que no hagi redactat
aquest manual que faci una restauració real a un host auxiliar utilitzant
només aquest document.
Fitxers
Els fitxers locals es troben al volum files. Feu-ne una còpia alhora que la
base de dades i restaureu-les totes dues juntes:
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 .
Si utilitzeu emmagatzematge d’objectes, activeu el control de versions del
bucket i conserveu durant 35 dies les versions substituïdes. Així, la
recuperació de fitxers a un moment concret la proporciona el mateix bucket.