Skip to content

Backup & Restore Toolkit

By Romi Fadli, S.Kom. 12 min read Backup & Recovery
On this page
A database, an object storage bucket, and a telemetry store on the left, each flowing into a stack of restore points on the right, the front one showing a timeline and a check mark

Backup & Restore Toolkit adalah tiga perintah yang mem-backup stack Docker yang sedang berjalan, me-restore-nya ke titik waktu mana pun, dan menjaga backup-backup itu agar tak tumbuh sampai disk penuh. Ia mencakup tiga jenis data yang biasa disimpan service saya — database PostgreSQL, file di object storage MinIO, dan telemetry di Elasticsearch — beserta konfigurasi yang membuat semuanya bisa dipakai, dan tak pernah mematikan satu service pun untuk melakukannya.

Backup-nya juga menjadi sumber setiap environment staging yang saya bangun, jadi ia di-restore dan diperiksa setiap rilis, bukan cuma pada hari ketika ada yang rusak. Backup yang tak pernah di-restore hanyalah harapan; backup yang di-restore setiap minggu adalah fakta.

Sekilas

Toolsbackup-production, restore-from-backup, remove-backup
BentukScript command-line; backup dan pembersihan berdiri sendiri, dan hanya restore yang memuat library pembantu bersama
BahasaBash, yang mengendalikan Docker, pg_basebackup dan pg_combinebackup, MinIO client, dan rsync
StatusStabil, berjalan di production
Kode sumberPrivat
Apa toolkit ini, dibangun dengan apa, dan keadaannya.

Yang dikerjakannya

A. Mem-backup tiga jenis data, masing-masing dengan caranya

Tak ada satu teknik backup yang cocok untuk ketiganya. PostgreSQL di-backup dengan pg_basebackup, berupa salinan penuh lalu incremental di tingkat blok. MinIO di-mirror objek demi objek. Elasticsearch di-backup lewat snapshot API miliknya sendiri, karena menyalin folder data mesin pencari yang sedang berjalan menghasilkan salinan yang belum tentu bisa dibuka. Ketiganya berjalan selagi service tetap melayani. Konfigurasi tiap modul juga ikut disimpan, karena pemulihan penuh butuh lebih dari sekadar datanya. Database yang sebenarnya bisa dibangun ulang oleh seeder pun tetap di-backup, supaya pemulihan tak pernah bergantung pada seeder maupun data upstream yang dibacanya.

B. Membangun titik pemulihan yang hanya berongkos perubahannya

Setiap titik pemulihan object storage tampak sebagai salinan lengkap, padahal file yang tak berubah hanyalah hard link ke titik sebelumnya, jadi hanya perubahannya yang memakan ruang. Pada satu pengukuran, tiga titik yang masing-masing berisi 101 MB hanya memakai 102 MB di disk, bukan 303 MB. File yang dihapus dari storage lenyap dari titik terbaru tapi tetap ada di titik-titik lama, dan justru itulah yang membuat kembali ke belakang berguna. Penghematan itu bergantung pada satu salinan kerja bersama: ketika tiap titik mengunduh ke folder staging miliknya sendiri, setiap objek tiba dengan inode baru, hard link tak pernah terbentuk, dan tiga titik memakan 303 MB. MinIO client juga dijalankan sebagai user dan group saya sendiri, karena file yang dulu ditulisnya sebagai root membuat rotasi gagal dengan permission denied.

C. Me-restore ke titik mana pun

Restore membangun ulang setiap data directory PostgreSQL dari backup penuh beserta rantai incremental-nya, sampai titik terakhir atau sampai titik yang saya sebut. Versi database yang dipakai untuk membangun ulang satu backup dibaca dari backup itu sendiri, jadi backup yang diambil sebelum stack di-upgrade tetap bisa dibangun ulang sesudahnya. Database tujuan yang masih berjalan dimatikan oleh restore itu sendiri, dan prosesnya baru lanjut setelah database benar-benar mati. Direktori hasil bangun ulang diberi recovery signal dan restore command, lalu keduanya diperiksa lagi: tanpa signal itu, entrypoint image akan menginisialisasi database baru di atas hasil restore dan meninggalkan environment kosong tanpa suara. Prefix tujuan bawaannya milik staging, jadi production tak terjangkau kecuali saya memintanya, dan container berjalan sebagai user saya serta mengambil alih folder milik root tanpa hak lebih tinggi, sambil mencetak perintah persisnya bila tak berhasil.

D. Me-restore object storage dan telemetry seperti semula

Object storage di-mirror balik dari titik yang dipilih dengan penghapusan dinyalakan, karena tanpanya hasilnya adalah titik itu digabung dengan isi yang sudah ada, bukan titik itu sendiri — perbedaan yang terbukti saat menguji restore ke titik yang lebih lama. Policy akses bucket publik dipasang lagi dari file berversi, karena mirror tak membawa policy. Untuk telemetry, data stream yang akan bertabrakan dihapus lebih dulu, karena data stream tak bisa sekadar ditutup, baru snapshot-nya di-restore. Sesudahnya user dan role telemetry dibuat ulang, lalu collector dan dashboard di-restart, supaya backup lama yang tak menyimpan security index tetap kembali dalam keadaan bisa ditulisi.

E. Menyatakan seberapa parah kegagalannya

Perintah backup tak sekadar melaporkan berhasil atau gagal. Ia membedakan backup yang tak bisa dipakai dari backup yang hanya kehilangan telemetry-nya, jadi job terjadwal bisa langsung bereaksi pada yang pertama dan santai menangani yang kedua. Backup yang tak menangkap satu database pun dianggap error, jadi stack production yang mati tak pernah tampak sebagai malam yang sukses di cron. Store yang container-nya tak berjalan dilewati dengan catatan, bukan digagalkan.

F. Menjaga retensi agar tak berubah jadi penghapusan

Backup penuh yang lama dihapus sesudah backup baru dibuat, dan perintah pembersihan juga bisa dijalankan dengan jadwalnya sendiri. Perintah pembersihan menolak menyisakan kurang dari satu backup, tak pernah menyentuh repository snapshot bersama maupun salinan kerja yang dibutuhkan mirror berikutnya, dan melaporkan ruang yang benar-benar dibebaskan, dengan menghitung file ber-hard link satu kali saja. Pembersihan punya dry run dan daftar yang menampilkan ukuran tiap backup penuh beserta titik-titiknya, penanda backup terbaru, dan snapshot telemetry yatim. Snapshot telemetry milik satu backup penuh dihapus lewat API mesin pencari sebelum folder-nya, karena nama snapshot itu tersimpan di folder tersebut — sembilan snapshot yatim sempat menumpuk sebelum urutan ini diperbaiki — dan penunjuk ke backup terbaru diperbaiki atau dihapus bila tak lagi menunjuk ke backup mana pun.

Cara saya memakainya

bash
backup-production --full              # new full backup, rotate old ones
backup-production --incremental       # changes since the last point
restore-from-backup --target-dir staging --list-chain
restore-from-backup --target-dir staging --upto 20260818-102021
remove-backup --keep 2 --prune-es     # retention, plus orphaned snapshots
Exit codeArtiYang dilakukan
0setiap store yang berjalan sudah ter-backup; store yang mati dilewati dengan catatanbaca catatannya
1PostgreSQL atau MinIO gagal; backup itu tak sah untuk restoretangani sekarang
2hanya snapshot Elasticsearch yang gagal, jadi dokumen dan file amanperiksa log; telemetry bisa menyusul
Cara perintah backup melaporkan hasilnya.

Di production ia dijalankan cron. Jadwalnya dibentuk oleh nilai datanya dan kapasitas disknya. Backup penuh mengikuti hari dalam minggu, bukan setiap hari ketujuh, karena langkah tujuh hari dimulai ulang tiap bulan dan menyisakan celah dua hari di pergantian bulan. Titik pemulihan terikat pada interval, yang saya pilih dengan menimbang nilai datanya terhadap ongkos menjalankannya lebih sering. Jumlah yang disimpan perintah pembersihan harus sama dengan milik backup.

Backup penuhmingguan, menyimpan dua
Backup incrementalbeberapa kali sehari
Recovery point objectiveperubahan beberapa jam terakhir
Seberapa jauh ke belakangsekitar 14 hari, dua backup penuh mingguan
Ukuransekitar 52 GB per backup penuh; rotasi berjalan sesudah backup penuh yang baru masuk, jadi puncaknya menyimpan tiga, sekitar 156 GB
Jadwal yang berjalan di production, dan apa yang diberikannya.

Keputusan rancangan

Tanpa downtime, dan itu saya ukur, bukan saya anggap. Setiap teknik yang dipakai toolkit ini bekerja pada service yang hidup: base backup mengambil snapshot di awal dan menangkap tulisan sesudahnya di write-ahead log, mirror konsisten per objek, dan snapshot adalah cara resmi yang didukung mesin pencarinya. Saya mengujinya dengan menulis sekitar dua juta baris selama backup incremental berjalan; backup-nya berhasil, dan restore menghasilkan jumlah baris yang persis sama. Sepanjang backup, service menjawab tanpa satu error pun. Hanya restore yang menghentikan database, dan di production itu memakan sekitar tujuh detik.

Exit code menyatakan nilai dari apa yang hilang. Dokumen dan tanda tangan tinggal di PostgreSQL dan MinIO; Elasticsearch menyimpan telemetry. Bila kedua jenis kegagalan memicu alarm yang sama, satu snapshot telemetry yang gagal akan mengajari siapa pun pembaca alarm untuk mengabaikannya — dan alarm berikutnya yang diabaikan bisa jadi justru yang penting. Memisahkan keduanya membuat jadwal bisa memperlakukan kegagalan yang lebih ringan secara eksplisit, tanpa kebiasaan menambahkan || true yang akan menyembunyikan kegagalan yang serius. Kebiasaan itulah yang dulu dimiliki script ini: setiap kegagalan ditelan dan run diakhiri dengan sukses, sehingga snapshot telemetry yang gagal mungkin sudah berbulan-bulan dilaporkan baik-baik saja ke cron.

Ia berjaga terhadap kegagalan yang tampak seperti sukses. Dua kegagalan pernah terjadi di server sungguhan, dan tak satu pun menghasilkan error saat backup dibuat; keduanya baru muncul saat restore. Backup incremental menyimpan rujukan cache ke basisnya, yang bisa bertahan lebih lama daripada basis itu sendiri sesudah rotasi dan menghasilkan incremental yang tak pernah bisa digabungkan — karena itu cache kini dibandingkan dengan manifest yang benar-benar ada di disk, dan basis diambil ulang bila keduanya berbeda. Mesin pencari menandai repository snapshot-nya read-only bila foldernya disentuh dari luar, dan sesudah itu setiap snapshot gagal — karena itu toolkit mengenali pesan tersebut, menghapus registrasinya saja, bukan file-nya, mendaftarkan ulang repository-nya, lalu mencoba tepat sekali lagi, tanpa berputar. Kini keduanya mengumumkan diri di log.

Backup baru muncul ketika sudah lengkap. Setiap backup ditulis ke folder kerja tersembunyi dan baru dipindah ke tempatnya di langkah paling akhir, jadi backup setengah jadi tak pernah tampil sebagai titik pemulihan. Folder induknya dibuat sebelum pemindahan terakhir itu, karena perintah pindah tak membuatnya, dan versi awal pernah kehilangan seluruh kerjanya tepat di langkah terakhir. Setiap penghapusan dijaga supaya variable kosong tak pernah berubah jadi penghapusan dari root, dan folder yang tak ada tak lagi menghentikan script diam-diam di bawah penanganan error yang ketat. Titik penuh dan incremental memakai satu susunan, jadi restore memperlakukan keduanya sama, dan backup bersusunan lama dimigrasi otomatis dengan memindahkan, bukan menyalin, sehingga hard link-nya selamat dan ukurannya tak berlipat.

Telemetry kembali bersama user yang menulisnya. Snapshot tanpa security index mesin pencari mengembalikan telemetry tapi tidak user dan role milik collector; collector lalu ditolak di setiap ekspor dan membuang semuanya, dan butuh dua hari sampai ketahuan di production. Kini snapshot menyertakan index itu dan global state-nya, dan restore membuat ulang user-nya sebagai jaring pengaman bagi backup lama. Repository snapshot tinggal bersama backup, bukan di dalam folder environment, karena menghapus folder production akan ikut menghapus seluruh backup telemetry; kepemilikannya dibetulkan lewat container berumur pendek, dan bila mesin pencari belum punya path repository, snapshot dilewati sambil mencetak perintah untuk menyalakannya.

Ia berjalan dari direktori mana pun, tanpa hak lebih tinggi. Path data dan backup diambil dari nama project, bukan dari letak repository, jadi path-nya sama di laptop saya dan di server, dan script-script ini bisa dijalankan dari working directory mana pun. Backup dan pembersihan sengaja tak memuat library bersama, jadi masing-masing bisa disalin dan dijalankan sendiri.

Kembali ke belakang itu murah, dan membersihkan itu aman. Titik pemulihan murah berkat hard link, dan setiap backup penuh berdiri sendiri: menghapus satu terbukti membiarkan setiap file di backup lain tetap identik. Restore bisa berhenti di titik bernama mana pun, tapi titik itu dicocokkan persis, jadi saya menampilkan rantainya lebih dulu lalu menyalin nilainya. Perintah pembersihan menolak menghapus semuanya, keluar dengan sukses bila tak ada yang perlu dihapus supaya job bulanan tak mengirim laporan gagal tiap bulan, dan membuang snapshot telemetry yatim yang tak terlihat oleh rotasi biasa.

Spesifikasi

MencakupPostgreSQL 17 atau lebih baru dengan ringkasan WAL dinyalakan, empat database; MinIO; Elasticsearch lewat snapshot API-nya; file konfigurasi dan environment tiap modul
KeluaranSatu folder per backup penuh berisi basis dan incremental-nya, manifest per titik, dan penunjuk ke backup penuh terbaru
DowntimeTak ada saat backup; sekitar tujuh detik untuk restore
Sasaran restoreSemua data, atau PostgreSQL, MinIO, atau telemetry saja; sampai titik terakhir atau titik yang disebut
KeamananRestore mematikan sendiri database tujuan yang berjalan dan baru lanjut setelah mati; perintah pembersihan menyisakan setidaknya satu backup penuh; repository bersama tak pernah dihapus
PrasyaratDocker; folder backup di satu filesystem supaya hard link berfungsi; cron untuk penjadwalan
Cakupan, jaminan, dan prasyarat toolkit ini.

Terkait

Cerita di balik script-script ini, termasuk dua kegagalan yang tampak seperti sukses.

Setiap script mengikuti bentuk yang saya berikan pada setiap tool kecil, dalam varian Bash-nya.

Prosedur rilis saya membangun setiap environment staging dari backup ini, dan itulah yang membuktikan backup-nya bisa di-restore.

Kenapa telemetry dipindah lewat snapshot API: portabel lintas versi dan menyisakan titik untuk kembali.

Kenapa setiap environment staging lahir dari backup ini, bukan dari database yang sedang hidup.

Kenapa telemetry butuh user dan role sendiri, yang dibuat ulang oleh restore.

Istilah

Base backup
Salinan fisik lengkap dari data directory PostgreSQL, diambil selagi database berjalan.
Backup incremental
Backup yang hanya berisi blok yang berubah sejak backup sebelumnya, dan digabungkan dengannya saat restore.
Write-ahead log (WAL)
Catatan perubahan yang ditulis PostgreSQL sebelum menerapkannya, diputar ulang agar backup konsisten.
Hard link
Nama kedua untuk file yang sama di disk, sehingga dua folder bisa berbagi file tanpa menyimpannya dua kali.
Recovery point objective (RPO)
Jumlah data terbaru paling banyak yang bisa hilang, diukur sebagai waktu sejak backup terakhir.
Repository snapshot
Lokasi tempat Elasticsearch menyimpan snapshot-nya, dengan data yang dipakai bersama di antara snapshot-snapshot itu.

Technologies covered

  • Bash
  • rsync
  • Elasticsearch
  • PostgreSQL
  • Docker
  • MinIO

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

An old layout with a grey banner and a new structure with a green banner, each holding a blue database, a purple document store, and an orange telemetry store; three coloured paths pass through one export package marked with a green check

Empat perintah yang memindahkan stack dari layout lama ke struktur baru melintasi versi mayor: export tanpa mengubah data sumber, import tiap store sesuai cara yang diizinkan versinya, verifikasi apa yang benar-benar tiba, dan rename modul sambil memindahkan datanya.

11 min read Read more →
Four identical grey keys on the left each become a key of its own colour on the right, every new key marked with a green check; below, a purple key stays behind a red bar

Satu perintah yang merotasi kredensial internal satu environment kunci demi kunci: dry run sebagai default, backup lebih dulu, nilai unik untuk tiap kunci, login sungguhan untuk membuktikan tiap kredensial yang bisa dipakai login, dan rollback bagi yang gagal.

8 min read Read more →
A staging configuration with an orange banner and a production configuration with a green banner; ordinary keys cross over with arrows, while a locked secret is stopped halfway by a red cross and production keeps its own lock

Tiga perintah yang membawa rilis teruji ke production tanpa menimpa apa yang dimilikinya: mempromosikan struktur konfigurasi tapi mempertahankan rahasia dan alamatnya, menolak menyalin rahasia baru, dan menerapkan migration yang dibawa image.

10 min read Read more →
Three sources on the left — configuration, container images, and a backup — flowing into one staging environment with an orange banner, its services, and a healthy check mark

Tiga perintah yang membangun salinan penuh production untuk satu rilis — konfigurasi dari repository, image dari registry, data dari backup terbaru — me-restore-nya berurutan, menjalankan migration rilis terhadap data itu, dan membuangnya tanpa jejak.

13 min read Read more →
A purple root circle linked to a blue and an orange intermediate circle, which lead to a signed document with a green seal and to a clock.

Aurograf

DevOps

Platform tanda tangan digital Pemerintah Kota Tangerang Selatan: penanda tangan membuktikan identitasnya, menandatangani PDF hingga PAdES B-LTA dengan sertifikat dari otoritas milik platform sendiri, dan siapa pun bisa memverifikasinya — versi 2, yang saya rancang, bangun, dan operasikan.

10 min read Read more →

Related Articles

A stack of Markdown documents and an orange arrow leading to a browser page with a side menu, a heading, paragraphs, a dark code panel, and a table of contents with its active entry highlighted

Satu script yang mengubah dokumentasi Markdown satu project menjadi situs statis yang terbuka dari folder tanpa server maupun internet: menu, daftar isi yang mengikuti pembaca, tabel yang tak pernah terpotong, kode dengan tombol salin, dan tautan yang tetap berfungsi.

7 min read Read more →
An old layout with a grey banner and a new structure with a green banner, each holding a blue database, a purple document store, and an orange telemetry store; three coloured paths pass through one export package marked with a green check

Empat perintah yang memindahkan stack dari layout lama ke struktur baru melintasi versi mayor: export tanpa mengubah data sumber, import tiap store sesuai cara yang diizinkan versinya, verifikasi apa yang benar-benar tiba, dan rename modul sambil memindahkan datanya.

11 min read Read more →
Three image layer stacks, each with a version tag of its own colour, blue, orange, and green, each leading to an archive file banded in the same colour

Perintah yang memberi versi tersendiri pada tiap modul stack, mem-build image-nya di bawah tag yang berarti tepat satu build, menolak memakai ulang versi atau menelan compile yang gagal, dan mengarsipkan image menurut nama dan tag supaya versi tak saling menimpa.

7 min read Read more →