Rancangannya dijelaskan di bagian 8 docs/architecture/23-ops.md. Ini adalah
runbook untuk produk Docker Compose. Dokumen ini ditulis agar dapat diikuti oleh
seseorang yang tidak menulisnya; jika ada langkah yang tidak jelas, berarti ada
kekurangan dalam dokumen ini.
Apa yang dilindungi dan bagaimana caranya
Aset
Cara
Lokasi
Basis data
WAL diarsipkan terus-menerus, paling lama setiap 60 detik, sejak boot pertama
Volume pgwal
Basis data
Cadangan dasar dengan pg_basebackup, secara bawaan setiap hari (backup-scheduler)
Volume pgbackup
Basis data
Salinan terenkripsi dari cadangan dasar dan WAL, setiap lima menit (backup-offsite)
Penyimpanan terpisah yang Anda tentukan
Berkas
Volume files. Salin dengan alat pencadangan host Anda, atau gunakan penyimpanan objek berversi
Volume files
Rahasia
docker/.env, terutama QUIRE_MASTER_KEY (dan QUIRE_MASTER_KEY_RETIRED yang masih digunakan), QUIRE_BACKUP_ENCRYPTION_KEY, serta docker/secrets/audit-signing-key.pem
Simpan salinannya di luar host ini
Indeks pencarian, cache, hasil olahan
Tidak dicadangkan; dibuat ulang
Sasaran: titik pemulihan dalam rentang 60 detik sebelum kegagalan, dan pemulihan
dalam 60 menit untuk basis data 500 GB.
Dua kesalahan umum: basis data yang dipulihkan tanpa berkasnya akan
menampilkan halaman rusak. Basis data yang dipulihkan tanpa
QUIRE_MASTER_KEY tidak dapat mendekripsi kredensial SSO, webhook, dan
integrasi yang tersimpan di dalamnya. Sampai rotasi kunci utama selesai tanpa
nilai yang belum terselesaikan (key-rotation.md), hal ini juga
mencakup kunci yang dipensiunkan. Keduanya merupakan bagian dari cadangan.
Membuat cadangan
Cadangan dasar seluruh klaster:
docker compose -f docker/compose.yaml --profile backup run --rm backup
Proses ini menyimpan QUIRE_BACKUP_KEEP cadangan dasar terbaru (bawaan 5) dan
menghapus WAL tertua yang sudah tidak diperlukan, sehingga arsip tidak tumbuh
tanpa batas. Jadwalkan setiap hari dengan cron atau pewaktu systemd di host:
15 2 * * * cd /srv/quire && docker compose -f docker/compose.yaml --profile backup run --rm backup >> /var/log/quire-backup.log 2>&1
Atau biarkan tumpukan menjadwalkannya: profil backup menjalankan
backup-scheduler, yang membuat cadangan dasar setiap
QUIRE_BACKUP_INTERVAL_HOURS (bawaan 24), serta backup-offsite yang dijelaskan
berikutnya.
docker compose -f docker/compose.yaml --profile backup up -d
Salinan terenkripsi di luar host
Kedua volume berada di host yang sama dengan basis data, dan cadangan di mesin
yang mengalami kegagalan bukanlah cadangan. backup-offsite menyalin setiap
cadangan dasar dan setiap segmen WAL yang diarsipkan ke penyimpanan terpisah
melalui port penyimpanan, mengenkripsinya, lalu menyimpannya dengan kebijakan
retensi:
Enkripsi. AES-256-GCM dengan QUIRE_BACKUP_ENCRYPTION_KEY (atau berkas
yang ditunjuk oleh QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 byte, dibuat dengan
openssl rand -hex 32. Setiap berkas memiliki nonce dan tag autentikasinya
sendiri, sehingga salinan tidak dapat dibaca tanpa kunci dan setiap perubahan
akan terdeteksi. Simpan kunci bersama QUIRE_MASTER_KEY, jauh dari host ini
dan jauh dari penyimpanan cadangan. Tanpa kunci, pemulihan tidak mungkin
dilakukan.
Lokasi.QUIRE_BACKUP_STORAGE_DRIVER bernilai s3, azure, atau local
(disk jarak jauh yang dipasang di QUIRE_BACKUP_STORAGE_ROOT). Pengaturannya
sama dengan pengaturan penyimpanan berkas, tetapi memakai awalan
QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET,
QUIRE_BACKUP_S3_ACCESS_KEY_ID, dan seterusnya. Gunakan bucket yang berbeda,
dan idealnya akun yang berbeda dari penyimpanan berkas; gunakan kredensial yang
dapat menulis tetapi tidak menghapus jika penyedia mengizinkannya.
Retensi. Cadangan dasar terbaru sebanyak QUIRE_BACKUP_OFFSITE_KEEP
(bawaan QUIRE_BACKUP_KEEP, atau 7 jika tidak disetel) dan WAL yang diperlukan
oleh cadangan paling lama di antaranya; set dan segmen yang lebih lama dihapus
dari penyimpanan.
Waktu. Setiap QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (bawaan 300 detik).
Pengiriman bersifat idempoten: data yang sudah tersimpan dilewati, dan
cadangan dasar baru dianggap tersimpan setelah manifesnya ditulis sebagai
langkah terakhir.
Perintah yang sama dapat dijalankan secara manual:
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
Untuk memulihkan di host baru, tarik satu set cadangan terlebih dahulu, lalu
ikuti langkah-langkah di bawah dengan direktori hasil pengambilan sebagai
pengganti volume pgbackup dan direktori hasil pengambilan wal-archive
sebagai pengganti pgwal:
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
Memulihkan ke titik waktu tertentu
Gunakan prosedur ini setelah kehilangan data: impor yang keliru, kursus yang
terhapus, atau migrasi kontrak yang perlu dibatalkan. Prosedur ini mengganti
basis data aktif, jadi latih dahulu menggunakan latihan di bawah.
Pilih waktu sasaran, dalam UTC, sesaat sebelum kerusakan terjadi:
2026-09-24 09:30:00+00. Log audit (/admin/audit) biasanya menunjukkan
waktunya.
Hentikan semua proses yang menulis:
docker compose -f docker/compose.yaml stop web content worker scheduler collab
Pertahankan klaster yang rusak sampai pemulihan terverifikasi:
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/
Bongkar cadangan dasar terbaru yang lebih lama dari waktu sasaran ke
volume data, lalu minta pemulihan terarah:
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'
Pulihkan: jalankan Postgres sekali dengan pengaturan pemulihan, melalui
override Compose agar berkas normal tidak tersentuh:
Override ini mengganti seluruh perintah, jadi dua pengaturan yang diperlukan
untuk pemulihan harus dicantumkan kembali: max_connections tidak boleh
lebih rendah daripada milik primer (jika tidak, pemulihan berhenti dengan
pesan “insufficient parameter settings”) dan pg_hba.conf yang dipasang.
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"
Periksa hasilnya sebelum mengizinkan siapa pun masuk: rantai audit
(docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts),
dan pastikan data yang hilang sudah kembali.
Kembali ke operasi normal: docker compose -f docker/compose.yaml up -d.
Postgres akan dimulai ulang dengan pengarsipan aktif, dan linimasa WAL baru
dimulai. Segera buat cadangan dasar yang baru.
Nama proyek quire menjadi awalan setiap volume; docker volume ls menampilkan
nama persisnya.
Latihan verifikasi terjadwal
backup-offsite juga menjalankan latihan setiap
QUIRE_BACKUP_DRILL_INTERVAL_HOURS (bawaan 168, mingguan), dan sekali lagi pada
putaran berikutnya jika terjadi kegagalan. Proses ini mengambil cadangan dasar
terbaru dari luar host dan setiap segmen WAL setelahnya, mendekripsi semuanya
(membuktikan kuncinya masih dapat membukanya dan tidak ada yang diubah),
membandingkan setiap berkas dengan manifesnya, memeriksa bahwa arsip merupakan
direktori data Postgres, serta memastikan WAL sejak cadangan tidak memiliki jeda.
Laporan ditulis ke penyimpanan sebagai reports/drill-<time>.json dan ke log
layanan; latihan yang gagal menyebutkan berkas atau segmen pertama yang hilang.
Latihan pemulihan
Cadangan yang belum pernah dipulihkan bukanlah cadangan. Latihan ini memulihkan
cadangan dasar lengkap terbaru sebelum waktu sasaran beserta arsip WAL ke Postgres
sementara yang tidak berbagi apa pun dengan instans aktif, lalu membuktikan
hasilnya:
docker/scripts/restore-drill.sh # to ninety minutes agodocker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
Waktu sasaran harus UTC dengan format persis seperti itu. Diperlukan cadangan
dasar yang lebih lama darinya dan WAL yang diarsipkan melewati waktu tersebut:
pada instalasi baru, buat cadangan dasar dan tunggu segmen berikutnya diarsipkan
(paling lama satu menit jika ada penulisan) sebelum memilih waktu sasaran setelah
cadangan itu. Latihan ini hanya memerlukan Docker dan bash di host.
Setiap langkah berikut dapat menggagalkan latihan:
Dapat dipulihkan: klaster sementara memutar ulang WAL hingga waktu sasaran
dan dapat dibuka.
Lengkap: jumlah baris setiap tabel dibandingkan dengan basis data aktif
(tooling/restore-drill). Basis data aktif sudah berubah sejak waktu sasaran,
jadi perbedaan tabel dapat mencapai nilai yang lebih besar antara 500 baris
dan sepersepuluh ukurannya, ke arah mana pun (penulisan membuatnya tertinggal,
penghapusan membuat hasil pemulihan memiliki lebih banyak baris); partisi yang
dibuat setelah waktu sasaran bukanlah tabel yang hilang. Pada instalasi yang
lebih sibuk, perluas toleransi dengan QUIRE_DRILL_MAX_BEHIND dan
QUIRE_DRILL_MAX_DRIFT_RATIO. Tabel yang hilang atau kosong akan menggagalkan
latihan.
Integritas: rantai hash audit terverifikasi pada salinan yang dipulihkan.
Dapat digunakan: peran aplikasi dapat membaca melalui keamanan tingkat
baris.
Waktu: dari mulai hingga hasil hijau, dibandingkan dengan
QUIRE_DRILL_RTO_SECONDS (bawaan 3600).
Latihan ini tidak pernah menulis ke basis data aktif ataupun volumenya: volume
cadangan dan WAL dipasang hanya-baca dan klaster sementara dihapus pada akhir
proses, baik berhasil maupun gagal.
Setel QUIRE_DRILL_REPORT ke suatu lokasi agar laporan JSON ditulis, baik
berhasil maupun gagal, lalu jalankan secara terjadwal dari host Docker:
Jalankan setiap bulan dan sebelum setiap peningkatan versi. Latihan yang gagal
menghalangi peningkatan versi. Setiap tiga bulan sekali, minta seseorang yang
tidak menulis runbook ini melakukan pemulihan titik waktu sungguhan di host
cadangan dengan hanya menggunakan dokumen ini.
Berkas
Berkas lokal berada di volume files. Cadangkan bersamaan dengan basis data,
pada waktu yang sama, dan pulihkan keduanya bersama-sama:
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 .
Untuk penyimpanan objek, aktifkan pembuatan versi bucket dan simpan versi lama
selama 35 hari; pemulihan titik waktu untuk berkas kemudian ditangani oleh
bucket itu sendiri.