Log yang terukur dan terkorelasi membuat insiden bisa dijelaskan — tetapi penjelasan bukan pemulihan. Ketika disk srv-toko benar-benar mati, satu-satunya hal yang menentukan apakah toko bangkit adalah salinan data yang hidup di luar disk itu, dan pertanyaannya selalu diajukan terlambat: apakah salinan itu lengkap, terbaru, dan pernah terbukti bisa dipulihkan. Aturan warisan industri menyebutnya tiga-salinan-dua-media-satu-luar — kerangka yang sudah kita janjikan pada Chapter Backup dan Restore Volume dan Backup Database — dan Docker memberi aturan itu wujud yang aneh tapi kuat: data produksi toko bisa hidup sebagai volume bernama, sebagai arsip tar, sekaligus sebagai dump SQL, sementara aplikasi itu sendiri bisa lahir ulang dari registry dengan digest yang identik bit-per-bit. Chapter ini merakit semuanya: strategi 3-3-1 versi Docker, skrip backup yang berjalan tiap malam, retensi dan salinan ke luar lokasi, lalu runbook pemulihan sembilan langkah yang ukurannya jujur — RPO 24 jam, RTO 60 menit — karena angka yang dipalsukan lebih berbahaya daripada musibah itu sendiri.
Aturan 3-3-1 dalam Kosakata Docker #
Klasik 3-2-1 menuntut tiga salinan di dua media berbeda dengan satu di luar lokasi. Versi Docker kita menegaskan bentuknya satu angka lebih tajam — tiga salinan di tiga bentuk berbeda dengan satu di luar lokasi — karena di dunia container, bentuk menentukan jalur pemulihan:
| Unsur | Wujud di toko | Lokasi |
|---|---|---|
| Salinan 1 — data hidup | volume toko_db-data dan toko_uploads yang sedang dipakai stack | disk data VPS produksi (/data) |
| Salinan 2 — arsip di staging | dump SQL terkompresi plus arsip tar, keduanya berlabel tanggal | disk VPS staging (media kedua, terpisah dari produksi) |
| Salinan 3 — off-site | salinan rsync dari seluruh arsip staging | server arsip di lokasi lain |
| Bentuk A — hidup | cluster PostgreSQL yang sedang melayani | pemulihan: tidak ada — ini sumbernya |
| Bentuk B — fisik | arsip tar isi volume mentah | pemulihan: ekstrak ke volume, cepat, terikat struktur berkas |
| Bentuk C — logis | dump SQL hasil pg_dump | pemulihan: replay ke server PostgreSQL mana pun, portabel lintas versi |
Tiga angka pertama melindungi dari kehilangan; tiga bentuk melindungi dari kejebakan satu jalur — dump SQL yang korup masih punya tar sebagai penyelamat, tar yang tak terbaca masih punya dump. Dan satu salinan di luar lokasi menutup skenario yang tak bisa dijawab dua lainnya: kebakaran, ransomware yang menyapu satu jaringan, atau kesalahan operator yang menular lintas mesin.
Dua keping lain melengkapi peta pemulihan sebelum kita menyentuh skrip. Pertama, infrastruktur adalah kode: repositori git toko memuat compose.yaml, Dockerfile multi-stage, nginx.conf, dan skrip migrasi — seluruh cara membangun stack. Kedua, image aplikasi bertag di registry adalah salinan keempat yang sering dilupakan: server pengganti tidak perlu meng-compile apa pun, cukup menarik registry.example.com/toko/backend:1.4.0 dan jarinya menunjuk digest yang sama:
$ docker inspect --format '{{index .RepoDigests 0}}' registry.example.com/toko/backend:1.4.0Output yang Dihasilkan:
registry.example.com/toko/backend@sha256:3f8c1a9d2e4b607159c28ad3f0b4e6d8a1c3e5f7b9d0f2a4c6e8b0d2f4a6c8e0Digest itu adalah identitas content-addressable yang kita kenal dari Chapter Image Digest dan buktikan lintas mesin pada Chapter Push dan Pull Image: docker pull di server baru mencetak Digest: yang identik karakter demi karakter, dan dua container yang lahir dari digest yang sama dijamin identik bit-per-bit — janji yang tak dimiliki satu pun prosedur backup berbasis berkas.
Skrip backup-toko.sh #
Ritual malaman toko hidup dalam satu skrip yang dibaca dari atas ke bawah dalam satu tarikan napas. Berkas /srv/toko-produksi/backup-toko.sh:
#!/usr/bin/env bash
set -euo pipefail
cd /srv/toko-produksi
STAGING=/srv/backup
OFFSITE=[email protected]:/srv/backup-toko
TANGGAL=$(date +%F)
mkdir -p "$STAGING"
docker compose exec -T db pg_dump -U toko toko \
| gzip > "$STAGING/toko-db-$TANGGAL.sql.gz"
docker run --rm \
-v toko_uploads:/data:ro \
-v "$STAGING:/backup" \
alpine:3.20 \
tar czf "/backup/toko-uploads-$TANGGAL.tar.gz" -C /data .
find "$STAGING" -name 'toko-*' -mtime +7 -delete
rsync -a "$STAGING/" "$OFFSITE/"
printf '%s dump=%s uploads=%s\n' \
"$(date '+%F %T')" \
"$(du -h "$STAGING/toko-db-$TANGGAL.sql.gz" | cut -f1)" \
"$(du -h "$STAGING/toko-uploads-$TANGGAL.tar.gz" | cut -f1)"Mari kita telaah enam blok kerjanya:
set -euo pipefail— skrip mati pada galat pertama (-e), menolak variabel kosong (-u), dan — yang paling penting untuk backup —pipefailmembuat pipapg_dump | gzipdilaporkan gagal bilapg_dumpdi kiri pipa yang gagal, bukangzipdi kanannya; tanpa pipefail, dump yang gagal setengah jalan masih menghasilkan berkas gzip yang tampak sehat.cd /srv/tokobeserta tiga variabel — skrip berdiri di folder project agardocker composemembaca compose.yaml yang benar; direktori staging, alamat off-site, dan tanggal dikunci sebagai variabel sehingga keduanya bisa diubah di satu tempat.- Dump logis lewat
docker compose exec -T db pg_dump— flag-Tmematikan alokasi TTY yang secara bawaan disiapkancompose exec; tanpa-T, output yang dipipakan terkontaminasi\r\ndan hasil dump rusak — jebakan yang kita bedah tuntas pada Chapter Backup Database. MVCC menjamin snapshot konsisten tanpa menghentikan layanan. - Arsip fisik lewat helper container — pola Chapter Backup dan Restore Volume: container alpine membaca volume
toko_uploadsdalam mode read-only dan menuangkan isinya ke arsip tar.gz di bind mount staging; trik-C /data .menjaga nama berkas di dalam arsip relatif dan bersih. - Retensi
find -mtime +7 -delete— arsip yang lebih tua dari tujuh hari dihapus dari staging; jendela seminggu penuh dengan tujuh generasi sebelum yang tertua pergi. rsync -ake off-site tanpa--delete— staging berputar tujuh hari, tetapi arsip off-site menumpuk lebih panjang; retensi bulanan diatur di sisi arsip, bukan disalinkan mentah dari rotasi staging. Barisprintfterakhir menorehkan satu baris log bertanggal dan berukuran — bukti malam itu yang akan kita baca berikutnya.
Mengapa database dan unggahan diperlakukan dengan alat yang berbeda adalah keputusan arsitektur, bukan kebiasaan sembarangan. Dump logis bertanya ke server PostgreSQL: tabel apa isinya, baris apa isinya — jawaban dituang sebagai SQL yang bisa dijalankan ulang di server mana pun, dan berkat MVCC, foto diambil konsisten pada satu titik waktu tanpa menghentikan layanan. Tar fisik sama sekali tidak bertanya — ia menyalin berkas mentah dari direktori data, dan karena itu hanya konsisten bila server database berhenti menulis; salinan fisik dari volume postgres yang sedang hidup adalah foto yang kabur, bukan backup. Untuk toko_uploads yang berisi berkas statis, tar justru sempurna: tidak ada mesin yang menulis setengah-berkas, sehingga arsip bisa diambil kapan pun tanpa jendela henti. Kombinasi keduanya — dump untuk yang transaksional, tar untuk yang statis — adalah pola yang bisa kita ulang di stack lain tanpa berpikir ulang.
cron dan Bukti Tiga Malam #
Skrip sekuat jadwal yang menjalankannya. Satu baris crontab root — user yang pasti punya akses daemon dan hak tulis ke /srv/backup:
$ sudo crontab -eBaris berikut masuk ke berkas crontab:
30 2 * * * /srv/toko-produksi/backup-toko.sh >> /srv/backup/backup-toko.log 2>&1Tiga hari kemudian, berkas log tersebut adalah bukti yang paling meyakinkan bahwa seluruh rantai bekerja:
$ cat /srv/backup/backup-toko.logOutput yang Dihasilkan:
2026-10-22 02:30:01 dump=1.4M uploads=86M
2026-10-23 02:30:01 dump=1.4M uploads=86M
2026-10-24 02:30:02 dump=1.5M uploads=91MMari kita telaah tiga baris bukti itu:
- Waktu yang konsisten — 02:30:01 dan 02:30:02; jarak satu-dua detik adalah jarak realistis antara detik cron dan momen
printfsetelah seluruh pekerjaan selesai. - Ukuran dump yang stabil lalu tumbuh pelan — 1.4M dua malam berturut lalu 1.5M; pertumbuhan selaras dengan pesanan baru yang masuk, dan lonjakan mendadak puluhan kali adalah tanda ada yang salah (atau ada yang benar-benar laris).
uploadsyang tumbuh — 86M ke 91M; unggahan gambar produk baru oleh admin tercatat setia. Baris yang hilang pada suatu malam berarti skrip gagal sebelumprintf— danset -ememastikan kegagalannya tercatat di log yang sama lewat2>&1.
RTO dan RPO yang Jujur #
Strategi backup yang selesai adalah strategi yang berani menyebut angka. Dua angka itu milik toko kita:
| Ukuran | Nilai toko saat ini | Ditentukan oleh |
|---|---|---|
| RPO — berapa banyak data yang boleh hilang | maksimal 24 jam | jadwal backup malaman pukul 02:30: musibah total pukul sore mencuci bersih seluruh pesanan sejak dini hari |
| RTO — berapa lama toko boleh mati | maksimal 60 menit | runbook sembilan langkah yang telah dilatih dan berjalan di bawah satu jam pada drill terakhir |
Kedua angka itu jujur untuk skala toko: pelanggan bisa menerima pesanan kemarin hilang dan toko tutup satu jam. Bila bisnis tumbuh dan RPO 24 jam tak lagi bisa diterima, arah berikutnya adalah streaming replication PostgreSQL dengan replika siaga — wilayah yang berada di luar cakupan Course ini. Yang tidak boleh terjadi adalah angka yang tidak pernah dihitung: RTO yang sebenarnya empat jam karena runbook tidak pernah dilatih adalah kejutan yang menagih tepat saat panik.
Runbook Pemulihan Sembilan Langkah #
Angka RTO hanya sekuat kertas yang menuntunnya. Runbook berikut tertulis di RUNBOOK.md dalam repositori toko — ditarik bersama kode, diperbarui bersama rilis, dan dilatih bersama drill:
RUNBOOK PEMULIHAN TOKO — revisi 2026-10-24
1. Nyalakan VPS pengganti Ubuntu 24.04 dan jalankan runbook empat langkah
dari Chapter Docker di VPS sampai Docker Engine aktif dan ter-hardening.
2. git clone repositori deployment toko ke /srv/toko-produksi lalu
git checkout tag rilis yang berjalan saat musibah.
yang berjalan saat musibah.
3. docker login registry.example.com lalu docker compose pull — image
datang dengan digest identik, tak ada satu proses build pun.
4. Ciptakan volume produksi: docker volume create --label env=prod
toko_db-data dan toko_uploads.
5. Siapkan konfigurasi: salin env/produksi.env dari templat, tarik
secrets/db_password.txt dari vault, chmod 600.
6. docker compose up -d db — tunggu status Healthy di compose ps.
7. Pulihkan dump logis: zcat /srv/backup/toko-db-<tanggal>.sql.gz |
docker compose exec -T db psql -U toko -d toko -v ON_ERROR_STOP=1.
8. Pulihkan arsip fisik uploads lewat helper container tar — pola
restore dari Chapter Backup dan Restore Volume.
9. docker compose up -d untuk seluruh stack, smoke test
curl -fsSkI https://localhost dan curl /api/produk.json,
lalu catat total menit pemulihan di lembar drill.Urutan sembilan langkah itu bukan kebetulan yang bisa diacak: volume harus ada sebelum dump dituangkan, secrets harus ada sebelum db pertama menyala agar initdb membaca kata sandi yang benar, db harus sehat sebelum psql bisa menerima replay, dan seluruh stack hanya dinyalakan setelah datanya utuh. Langkah 7 memakai ON_ERROR_STOP=1 agar satu baris SQL yang gagal menghentikan restore di tempat — kegagalan yang terdengar adalah kegagalan yang bisa diperbaiki, sementara restore yang “berhasil setengah” adalah musibah kedua yang menunggu.
Data toko kini punya tiga salinan, tiga bentuk, satu rumah jauh, dan jalur pulang yang tertulis. Namun seluruh bangunan ini dibangun di atas satu asumsi yang jarang diucapkan: perubahan ke aplikasi datang sebagai peristiwa yang jarang dan terkendali. Bagaimana kita merilis versi baru ke produksi tanpa membawa seluruh toko turun, bagaimana dua warna stack bisa berdiri berdampingan, dan bagaimana kabur mundur ke versi lama dalam hitungan menit — lengkap dengan jebakan migrasi database yang tidak mau mundur sendiri — adalah panggung Chapter Update dan Rollback.
Referensi #
- Docker storage — volumes — pola resmi backup dan restore volume dengan helper container.
- PostgreSQL Documentation
— perilaku
pg_dump, konsistensi snapshot MVCC, danON_ERROR_STOP. - docker image inspect
— pembacaan
RepoDigestsuntuk memverifikasi identitas image. - docker compose exec
— flag
-Tuntuk eksekusi non-TTY di dalam skrip.