---
title: "Backup, Restauratioun op ene Zäitpunkt an den Restauratiounstest"
description: "Quire sécheren, op ene Zäitpunkt zrécksetzen an dat mam Restauratiounstest beweisen."
image: "https://docs.quirelms.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.quirelms.com/lb/llms.txt
> Use this file to discover all available pages before exploring further.

# Backup, Restauratioun op ene Zäitpunkt an den Restauratiounstest

<span id="backup-point-in-time-recovery-and-the-restore-drill"></span>

De Konzept ass `docs/architecture/23-ops.md` Kapitel 8. Dëst ass d'Operatiounsunweisung fir d'Docker-Compose-Versioun. En ass geschriwwen, fir vun engem gefollegt ze ginn, deen en net geschriwwen huet; wann e Schrëtt net kloer ass, ass dat en Defekt an dësem Dokument.

## Wat geschützt gëtt, a wéi <!--quire:what-is-protected-and-how-->

| Ressource | Wéi | Wou |
| --- | --- | --- |
| D'Datebank | WAL lückenlos archivéiert, héichstens all 60 Sekonnen, zënter dem éischten Boot | `pgwal`-Volume |
| D'Datebank | Basisbackup mat `pg_basebackup`, standardméisseg deeglech (`backup-scheduler`) | `pgbackup`-Volume |
| D'Datebank | Verschlësselt Kopien vun de Basisbackups an dem WAL, all fënnef Minutten (`backup-offsite`) | En getrennte Späicher, deen Dir nennt |
| Fichieren | D'`files`-Volume. Kopéiert se mat Ärem Backup-Tool vum Host, oder benotzt Objektspäicher mat Versiounen | `files`-Volume |
| Geheimnisser | `docker/.env`, virun allem `QUIRE_MASTER_KEY` (an all `QUIRE_MASTER_KEY_RETIRED`, déi nach am Gebrauch sinn), `QUIRE_BACKUP_ENCRYPTION_KEY`, an `docker/secrets/audit-signing-key.pem` | Halt eng Kopie ausserhalb vun dësem Host |
| Sichindexen, Cache, Renderungen | Net gesichert; nei gebaut |  |

Ziler: en Erhuelungspunkt, deen héichstens 60 Sekonnen no dem Ausfalen läit, an eng Restauratioun bannendrem 60 Minutten fir eng 500 GB-Datebank.

Zwee Feler sinn heefeg. Wann eng Datebank **ouni hir Fichieren** restauréiert gëtt, sinn d'Säiten kaputt. Eng Datebank, déi **ouni `QUIRE_MASTER_KEY`** restauréiert gëtt, kann d'Zougangsdaten fir SSO, Webhooks an Integratiounen, déi se hält, net entschlësselen; bis eng Rotatioun vum Master-Schlëssel ouni ongeléist Wäerter fäerig ass ([key-rotation.md](/lb/ops/key-rotation/)), dozou gehéieren och déi pensionéiert Schlësselen. Béid sinn Deel vum Backup.

## Backups huelen <!--quire:taking-backups-->

E Basisbackup vum ganzz Cluster:

```sh
docker compose -f docker/compose.yaml --profile backup run --rm backup
```

En hält déi neisten `QUIRE_BACKUP_KEEP` Basisbackups (standardméisseg 5) an de WAL ewechschneidet, deen net méi gebraucht gëtt, sou datt d'Archiv net ouni Grenz wuessen kann. Plangt en deeglech mat cron oder engem systemd-Timer op dem Host:

```cron
15 2 * * * cd /srv/quire && docker compose -f docker/compose.yaml --profile backup run --rm backup >> /var/log/quire-backup.log 2>&1
```

Oder loosst de Stack en plangen: de `backup`-Profile féiert `backup-scheduler` aus, deen all `QUIRE_BACKUP_INTERVAL_HOURS` (standardméisseg 24) en Basisbackup mécht, an `backup-offsite`, deen duerno beschriwwe gëtt.

```sh
docker compose -f docker/compose.yaml --profile backup up -d
```

## Verschlësselt Kopien ausserhalb vum Host <!--quire:encrypted-off-host-copies-->

Béid Volumes liewen op deemselwechten Host wéi d'Datebank, an e Backup op der Maschinn, déi ausgefall ass, ass keen Backup. `backup-offsite` kopéiert all Basisbackup an all archivéierten WAL-Segment iwwer de Späicherport, verschlësselt, an hält se do ënner Retentioun:

- **Verschlësselung.** AES-256-GCM mat `QUIRE_BACKUP_ENCRYPTION_KEY` (oder
  dem Fichier, deen duerch `QUIRE_BACKUP_ENCRYPTION_KEY_FILE` benannt gëtt):
  32 Bytes, vu `openssl rand -hex 32`. All Fichier huet seng eegen Nonce an
  en Tag fir d'Authentifikatioun, sou datt eng Kopie ouni de Schlëssel net
  liesbar ass an all Ännerung dran erkannt gëtt. Halt de Schlëssel mat
  `QUIRE_MASTER_KEY`, weit vun dësem Host an weit vum Späicher fir d'Backups.
  Ouni de Schlëssel gëtt et keng Restauratioun.
- **Wou.** `QUIRE_BACKUP_STORAGE_DRIVER` ass `s3`, `azure` oder `local` (eng
  Fern-Disk, déi op `QUIRE_BACKUP_STORAGE_ROOT` mountéiert ass). D'Astellungen
  sinn déi vum Fichierspäicher mat engem `QUIRE_BACKUP_`-Präfix:
  `QUIRE_BACKUP_S3_ENDPOINT`, `QUIRE_BACKUP_S3_BUCKET`,
  `QUIRE_BACKUP_S3_ACCESS_KEY_ID`, an dergleichen. Benotzt en aneren Bucket
  an, idealerweis, en aneren Kont wéi deen fir d'Fichieren, mat
  Zougangsdaten, déi schreiwen awer net läschen kënnen, wann de Ubidder et
  erlaabt.
- **Retentioun.** Déi neisten `QUIRE_BACKUP_OFFSITE_KEEP` Basisbackups
  (standardméisseg `QUIRE_BACKUP_KEEP`, soss 7) an de WAL, deen den alste
  vun hinne braucht; méi al Ensembles an Segmente ginn aus dem Späicher
  geläscht.
- **Wéini.** All `QUIRE_BACKUP_SHIP_INTERVAL_SECONDS` (standardméisseg 300).
  Dat Iwwerbréngen ass idempotent: wat scho gespäichert ass gëtt
  iwwersprongen, an e Basisbackup zielt eréischt dann als gespäichert, wann
  seng ManifestDatei als leschts geschriwwen ass.

D'Selwecht Kommando leeft vun Hand aus:

```sh
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts ship
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts verify
```

Fir op engem neie Host ze restauréieren, huelt éischten e Backup-Saz zréck, a fuert dunn déi Schrëtt hei drënner mat dem erofgeluedenen Ordner amplaz der `pgbackup`-Volume an dem erofgeluedenen `wal-archive` amplaz `pgwal`:

```sh
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
```

## Restauratioun op ene Zäitpunkt <!--quire:restoring-to-a-point-in-time-->

Benotzt dëst, wann Donnéeën verluer gi sinn: eng schlecht Importatioun, e geläscht Cours, eng Contract-Migratioun, déi Dir réckgängeg maache musst. En ersetzt d'Datebank, déi elo benotzt gëtt, also probéiert en éischte mam Test hei drënner.

1. **Wielt déi Zäit**, an UTC, grad virun dem Schied:
   `2026-09-24 09:30:00+00`. D'Audit-Log (`/admin/audit`) weist normalerweis
   de Moment.
2. **Stoppt alles, wat schreift**:
   `docker compose -f docker/compose.yaml stop web content worker scheduler collab`
3. **Halt de beschiedegten Cluster**, bis d'Restauratioun verifizéiert ass:
   ```sh
   docker compose -f docker/compose.yaml stop postgres
   docker run --rm -v quire_postgres18-data:/from -v quire_postgres-damaged:/to alpine cp -a /from/. /to/
   ```
4. **Packt de neisten Basisbackup, deen méi al ass wéi d'Zilzäit**, an den
   Donnéeë-Volume an frot no enger geziilter Erhuelung:
   ```sh
   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'
   ```
5. **Erhuelen**: start Postgres eemol mat den Astellungen fir d'Erhuelung,
   vun engem Compose-Override, sou datt déi normal Datei net beréiert gëtt:
   ```yaml
   # docker/compose.recover.yaml
   services:
     postgres:
       command: [postgres, -c, "restore_command=cp /var/lib/postgresql/wal-archive/%f %p",
                 -c, "recovery_target_time=2026-09-24 09:30:00+00",
                 -c, recovery_target_action=promote, -c, archive_mode=off,
                 -c, max_connections=200, -c, hba_file=/etc/postgresql/pg_hba.conf]
   ```
   Den Override ersetzt dat ganz Kommando, sou datt en déi zwee Astellungen
   erëmhëlt, vun deenen d'Erhuelung ofhänkt: `max_connections` net méi
   niddereg wéi beim Primä (soss brécht d'Erhuelung mat
   "insufficient parameter settings") an de mountéierte `pg_hba.conf`.
   ```sh
   docker compose -f docker/compose.yaml -f docker/compose.recover.yaml up -d postgres
   docker compose -f docker/compose.yaml logs -f postgres   # wait for "database system is ready"
   ```
6. **Kontrolléiert en**, ier een erangeet gëtt: d'Auditkette
   (`docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts`),
   an datt d'Donnéeën, déi verluer warn, erëm do sinn.
7. **Zeréck normal**: `docker compose -f docker/compose.yaml up -d`. Dat
   start Postgres nei op mat Archivéierung un, an eng nei WAL-Zäitlinn fänkt
   un. Huelt direkt en neien Basisbackup.

De Projetnumm `quire` steet viru all Volume; `docker volume ls` weist d'exakt Nimm.

## De geplanten Verifizéierungstest <!--quire:the-scheduled-verification-drill-->

`backup-offsite` féiert och all `QUIRE_BACKUP_DRILL_INTERVAL_HOURS` (standardméisseg 168, wöchentlech) e Test aus, an nach eng Kéier beim nächste Mol, nodeems en feelgeschloen ass. En holt de neisten Basisbackup, deen ausserhalb vum Host läit, an all WAL-Segment duerno, entschlësselt jiddereen (wat beweist, datt de Schlëssel se nach ëmmer opmaacht an näischt verännert ass), vergläicht all Fichier mat senger ManifestDatei, kontrolléiert, datt d'Archiv e Postgres-Donnéeë-Ordner ass, an kontrolléiert, datt de WAL zënter dem Backup keen Lach huet. De Bericht gëtt am Späicher an am Log vum Service geschriwwen, als `reports/drill-<time>.json`; e feelgeschloener Test nennt den Fichier oder den éischte fehlenden Segment.

## De Restauratiounstest <!--quire:the-restore-drill-->

E Backup, deen nach ni restauréiert gouf, ass keen Backup. De Test restauréiert de neisten kompletten Basisbackup, deen virun dem Zil gemaach gouf, zesumme mat dem WAL-Archiv, an en temporäre Postgres, deen näischt mam lopende Postgres deelt, a beweist d'Resultat:

```sh
docker/scripts/restore-drill.sh                                  # to ninety minutes ago
docker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
```

D'Zilzäit ass UTC a genauso an dëser Form. Si brauch e Basisbackup, deen méi al ass wéi si, an archivéierte WAL, deen no där Zäit archivéiert ass: bei enger neier Installatioun hëlt een e Basisbackup a waart, bis den nächsten Archivsegment dran ass (höchstens eng Minutt mat Schreiwen), ier een eng Zilzäit wielt, déi no dem Backup läit. De Test braucht Docker an bash op dem Host, soss näischt.

All Schrëtt feelt den Test:

1. **Erhuelbarkeet**: de Test-Cluster spillt bis op d'Zilzäit an opet sech.
2. **Vollstännegkeet**: d'Zuel vun den Zeilen vun all Tabell, verglach mat
   der Datebank, déi elo läuft (`tooling/restore-drill`). D'Datebank ass
   weidergelaaf zënter der Zilzäit, sou datt eng Tabell sech ëm méi kann
   ënnerscheeden wéi 500 Zeilen oder eng Zéngtel vun hirer Gréisst, an béide
   Richtungen (Schreiwen maachen, datt se zréckbleiwen, Läschtungen maachen,
   datt d'Restauratioun méi enthält); eng Partition, déi no der Zilzäit
   erstallt gëtt, ass keng verluer Tabell. Verbreet d'Toleranz bei enger méi
   vill benotzter Installatioun mat `QUIRE_DRILL_MAX_BEHIND` an
   `QUIRE_DRILL_MAX_DRIFT_RATIO`. Eng Tabell, déi feelt oder eidel gemaach
   ass, brécht den Test.
3. **Integritéit**: d'Audit-Hash-Kette gëtt op der restauréierter Kopie
   verifizéiert.
4. **Benotzbarkeet**: d'Applikatiounsroll lies iwwer d'Sécherheet op
   Zeilniveau.
5. **Zäit**: vum Start un bis den Test grénge gëtt, géint
   `QUIRE_DRILL_RTO_SECONDS` (standardméisseg 3600).

En schreift ni an d'Datebank, déi elo läuft, oder an hir Volumes: d'Volume fir Backups an WAL ginn nëmmen fir Liesen mountéiert an de Test-Cluster gëtt um Enn ewechgeholl, grénge oder net.

Setzt `QUIRE_DRILL_REPORT` op e Wee, fir en JSON-Bericht ze schreiwen, grénge oder net, a féiert en aus engem Plang vum Docker-Host aus:

```cron
30 3 1 * * cd /srv/quire && QUIRE_DRILL_REPORT=/var/log/quire-drill.json docker/scripts/restore-drill.sh >> /var/log/quire-drill.log 2>&1
```

Féiert en all Mount an ier all Upgrade aus. E feelgeschloener Test blockéiert den Upgrade. All dräi Méint loosst een een, deen dëst Runbook net geschriwwen huet, eng echt Restauratioun op ene Zäitpunkt op engem Reserve-Host maachen, mat nëmmen dësem Dokument.

## Fichieren <!--quire:files-->

Lokal Fichieren liewen am `files`-Volume, séiert se zesumme mat der Datebank, zur selwechter Zäit, a restauréiert béid zesummen:

```sh
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 .
```

Mat Objektspäicher aktivéiert Dir d'Versiounéierung vun de Buckets an hält d'Versioune vun den leschten 35 Deeg; d'Restauratioun op ene Zäitpunkt fir Fichieren ass dann déi vum Bucket selwer.

Source: https://docs.quirelms.com/lb/ops/backup-restore/index.mdx
