Skema katalog kini lahir otomatis dari folder init/, dan volume db-data telah terbukti setia melewati kematian container. Tapi dua jaminan itu bekerja pada dua musibah yang berbeda — container yang mati dan inisialisasi yang harus diulang — sementara musibah ketiga berdiri di luar jangkauan keduanya: volume itu sendiri hanya hidup di satu disk, dan docker volume rm yang kita jalankan pada awal chapter sebelumnya membuktikan betapa mudahnya satu perintah singkat memusnahkan seluruh isinya tanpa sidang. Database yang berisi data sungguhan menuntut salinan yang hidup terpisah dari container dan dari volume: sebuah berkas dump di host yang bisa disalin, dipindah, dan dibuka kapan pun. Chapter ini membangun ritual itu — dimulai dari perbedaan mendasar antara salinan logis dan salinan fisik, lalu pg_dump dalam tiga wujudnya, mysqldump untuk MySQL, jebakan pseudo-TTY yang paling sering mengotori hasil pipe, sampai otomatisasi harian lewat cron.
Dump Logis versus Salin Fisik #
Ada dua cara memindahkan isi database ke luar container, dan keduanya bekerja pada lapisan yang sama sekali berbeda. Salinan logis bertanya ke server: “tabel apa saja yang kau miliki, dan baris-baris apa isinya?” — jawabannya dituang sebagai perintah SQL yang bisa dijalankan ulang di server mana pun: CREATE TABLE, COPY, INSERT. Salinan fisik sama sekali tidak bertanya; ia menyalin berkas-berkas data mentah dari direktori PGDATA — PG_VERSION, base/, pg_wal/ — persis struktur yang kita intip lewat helper container pada Chapter Persistensi Database.
Perbedaan lapisan itu menentukan syarat kerjanya. pg_dump bekerja sebagai klien biasa: server menyajikan satu snapshot konsisten berkat MVCC (Multi-Version Concurrency Control), transaksi lain boleh terus menulis tanpa saling mengunci, dan hasil dump tetap utuh pada satu titik waktu — database boleh tetap hidup dan melayani aplikasi selama proses berjalan. Salinan fisik tidak punya kemewahan itu: berkas di disk bisa sedang setengah tertulis, WAL bisa sedang berada di tengah urutan penulisannya, dan salinan yang diambil saat server hidup adalah foto yang kabur — cluster hasilnya bisa menolak menyala, atau lebih buruk, menyala dengan data yang tidak utuh tanpa satu pun galat.
Backup Pertama dengan pg_dump #
Kita bekerja di folder proyek ~/katalog, dengan container db yang masih hidup dan berisi tiga baris produk hasil initdb. Perintah backup paling dasarnya sepanjang satu baris:
$ docker exec db pg_dump -U katalog katalog > backup-katalog.sqlTerminal tidak menampilkan apa pun — dan itu pertanda baik: seluruh stdout telah dialihkan ke berkas. Bukti kerjanya mendarat sebagai berkas host biasa:
$ ls -lh backup-katalog.sqlOutput yang Dihasilkan:
-rw-r--r-- 1 budi budi 18K Okt 13 22:19 backup-katalog.sqlMari kita intip isi berkas itu sebelum membedah jalurnya:
$ head -n 50 backup-katalog.sqlOutput yang Dihasilkan:
--
-- PostgreSQL database dump
--
-- Dumped from database version 18.4
-- Dumped by pg_dump version 18.4
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
...
SET search_path = public;
...
--
-- Name: produk; Type: TABLE; Schema: public; Owner: katalog
--
CREATE TABLE public.produk (
id integer NOT NULL,
nama text NOT NULL,
harga integer NOT NULL
);
ALTER TABLE public.produk OWNER TO katalog;
--
-- Name: produk_id_seq; Type: SEQUENCE; Schema: public; Owner: katalog
--
CREATE SEQUENCE public.produk_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.produk_id_seq OWNED BY public.produk.id;
--
-- Data for Name: produk; Type: TABLE DATA; Schema: public; Owner: katalog
--
COPY public.produk (id, nama, harga) FROM stdin;
1 Kopi Torabika 15000
2 Kopi Robusta Lampung 65000
3 Gula Aren Cair 45000
\.
...Baris-baris SET di tengah dipotong agar ringkas. Mari kita telaah jalur yang dilalui data dan apa isi berkas itu:
docker exec db pg_dump— pg_dump berjalan di dalam container, memakai klien bawaan image yang versinya (18.4) persis cocok dengan servernya; host tidak perlu memasang satu paket PostgreSQL pun.- stdout sebagai tali penghubung — pg_dump menulis SQL ke stdout prosesnya,
docker execmenyalurkan stdout itu ke terminal host, dan redirect>milik shell menangkap seluruh aliran ke berkasbackup-katalog.sqldi direktori kerja; tidak ada satu berkas pun ditulis di dalam container, jadi ukuran container tidak pernah membengkak karena backup. -U katalog katalog— user dan nama database yang dicadangkan; sesi ini berjalan sebagai klien biasa lewat socket lokal, tanpa menyentuh kata sandi karena autentikasi lokal container masih trust — kebiasaan image yang kita catat sejak chapter pertama section ini.CREATE TABLEdi atas,COPY ... FROM stdindi bawah — struktur dituang lebih dulu, menyusul data dalam format COPY yang ringkas; tiga baris produk kita tampak utuh sebagai teks tab-separated.- Yang tidak ada di dalamnya: role
pelaporan— pg_dump milik satu database hanya membawa objek database, bukan role yang hidup di tingkat cluster; siapa yang boleh membaca tetap urusan administrasi cluster, dan kita tangani konsekuensinya saat restore nanti. - Dump diambil saat server hidup — MVCC menjamin konsistensi; tiga baris produk berubah tidak sedikit pun selama proses, dan bila transaksi lain sedang menulis, hasilnya tetap foto utuh pada satu titik waktu.
Versi Ringkas: gzip #
Berkas 18K milik kita kecil karena databasenya kecil — database sungguhan menghasilkan ratusan megabyte SQL teks, dan teks SQL adalah bahan paling murah untuk dikompresi. Kompresi cukup dilakukan di sisi host, tanpa menyentuh container:
$ docker exec db pg_dump -U katalog katalog | gzip > backup-katalog.sql.gz
$ ls -lh backup-katalog.sql*Output yang Dihasilkan:
-rw-r--r-- 1 budi budi 18K Okt 13 22:19 backup-katalog.sql
-rw-r--r-- 1 budi budi 4.0K Okt 13 22:21 backup-katalog.sql.gzMari kita telaah bentuk baris perintahnya:
- Dua alat di dua dunia —
pg_dumpberjalan di dalam container,gzipberjalan di host; pipe|milik shell host menjadi jembatannya, dan tidak ada berkas sementara yang pernah ditulis ke disk sebelum hasil akhir. - 18K menjadi 4.0K — rasio lebih dari empat kali lipat pada contoh kecil kita; pada database produksi, rasio seperti 10:1 adalah hal biasa karena pola teks SQL yang sangat repetitif.
- gzip boleh diganti zstd atau xz — selama alatnya ada di host, pola pipenya tidak berubah sama sekali.
Format Kustom untuk pg_restore #
pg_dump punya bentuk ketiga yang bukan teks SQL sama sekali: format kustom -Fc, arsip biner terkompresi yang hanya dimengerti oleh pg_restore. Untuk lengkapnya kita simpan satu:
$ docker exec db pg_dump -U katalog -Fc katalog > backup-katalog.dump
$ ls -lh backup-katalog*Output yang Dihasilkan:
-rw-r--r-- 1 budi budi 18K Okt 13 22:19 backup-katalog.sql
-rw-r--r-- 1 budi budi 4.0K Okt 13 22:21 backup-katalog.sql.gz
-rw-r--r-- 1 budi budi 8.0K Okt 13 22:23 backup-katalog.dumpFormat kustom membuang kemampuan dibaca mata demi kemampuan yang jauh lebih berharga saat pemulihan: pg_restore bisa memilih hanya satu tabel lewat --table=produk, membangkitkan ulang objek lama dengan --clean --if-exists, bahkan mempercepat restore besar dengan beberapa koneksi paralel --jobs. Kedua opsi pertama itu akan kita lihat bekerja pada Chapter Restore Database. Praktik yang sehat menyimpan lebih dari satu bentuk: teks untuk dibaca manusia, kustom untuk pemulihan yang terkendali.
Backup MySQL dengan mysqldump #
Mesin MySQL kita dibersihkan pada Chapter Persistensi Database bersama volumenya, jadi mari hidupkan kembali — volume db-data-mysql yang baru lahir akan diisi ulang dari nol, dan tarian inisialisasi dua babaknya berlangsung belasan detik:
$ docker run -d --name db-mysql \
-e MYSQL_ROOT_PASSWORD=rahasia123 \
-e MYSQL_DATABASE=katalog \
-e MYSQL_USER=katalog \
-e MYSQL_PASSWORD=rahasia123 \
-v db-data-mysql:/var/lib/mysql \
mysql:8.4Output yang Dihasilkan:
c420e6e2c3ea9befc1867b9885b867a66703a28ecbe57bb4aad0a62cb02d26a4Tunggu hingga mysqld is alive menjawab ketukan mysqladmin — pola healthcheck dari Chapter Menjalankan MySQL — lalu tanam tabel dan data uji:
$ docker exec db-mysql mysqladmin ping -ukatalog -prahasia123
$ docker exec db-mysql mysql -ukatalog -prahasia123 katalog \
-e "CREATE TABLE produk (id INT AUTO_INCREMENT PRIMARY KEY, nama VARCHAR(100), harga INT);
INSERT INTO produk (nama, harga) VALUES ('Kopi Torabika', 15000), ('Gula Aren Cair', 45000);"Output yang Dihasilkan:
mysql: [Warning] Using a password on the command line interface can be insecure.
mysqld is alive
mysql: [Warning] Using a password on the command line interface can be insecure.Baris peringatan itu muncul dua kali karena dua perintah berjalan bergantian; mysqladmin menjawab di antaranya. Kini dump MySQL dalam bentuk yang didokumentasikan image resminya:
$ docker exec db-mysql sh -c 'exec mysqldump -ukatalog -prahasia123 katalog' > backup-katalog-mysql.sqlOutput yang Dihasilkan:
mysql: [Warning] Using a password on the command line interface can be insecure.Dan isi berkasnya — layak diintip, karena ada satu baris yang membedakannya dari pg_dump:
$ head -n 22 backup-katalog-mysql.sqlOutput yang Dihasilkan:
-- MySQL dump 10.13 Distrib 8.4.11, for Linux (x86_64)
--
-- Host: localhost Database: katalog
-- ------------------------------------------------------
-- Server version 8.4.11
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
...
--
-- Table structure for table `produk`
--
DROP TABLE IF EXISTS `produk`;
CREATE TABLE `produk` (
`id` int NOT NULL AUTO_INCREMENT,
`nama` varchar(100) DEFAULT NULL,
`harga` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
...Peringatan tadi justru bagian dari pelajaran. Mari kita telaah bentuk perintahnya dan isi berkasnya:
sh -c '...'melahirkan shell penafsir —docker execmenjalankan argumen apa adanya tanpa shell, jadi bentukVAR=nilai perintahatau penggabungan perintah tidak bisa ditafsirkan; membungkusnya dalamsh -cmemberikan satu shell untuk membaca string itu sebagai kode.execmenggantikan shell dengan mysqldump — tanpa exec, mysqldump berjalan sebagai anak shell dan sinyal penghentian terhenti di shell; dengan exec, mysqldump menjadi proses pengganti yang menerima sinyal langsung.- Peringatan pergi ke stderr, bukan stdout — mysqldump menuliskan SQL ke stdout dan keluhannya ke stderr; redirect
>hanya menangkap stdout, sehingga peringatan tetap terlihat di terminal sementara berkas hasil tetap bersih dari kalimat peringatan. - Isi berkasnya lebih siap menimpa daripada pg_dump — dump MySQL menulis
DROP TABLE IF EXISTSsebelum tiapCREATE TABLE, sehingga restore ke database yang sudah berisi akan menimpa dengan rapi; konsekuensi perbedaan ini kita saksikan langsung pada chapter berikutnya.
Masalahnya tinggal satu, dan kalimatnya memang pantas berdiri sendiri:
Dua pintu keluar yang bersih tersedia, keduanya memanfaatkan environment. Pintu pertama menyuntik variabel MYSQL_PWD yang dibaca langsung oleh mysqldump — perhatikan bahwa docker exec menerima flag -e persis seperti docker run:
$ docker exec -e MYSQL_PWD=rahasia123 db-mysql mysqldump -ukatalog katalog > backup-katalog-mysql.sqlPintu kedua menyiapkan berkas konfigurasi berisi kredensial lalu memanggilnya lewat --defaults-extra-file — pola yang lebih cocok saat satu host membackup banyak database sekaligus. Keduanya menghilangkan peringatan sekaligus jejak sandi di daftar proses.
Jebakan TTY saat Memipakan Output #
Ritual sejauh ini memakai docker exec polos — tanpa -i dan tanpa -t. Ada alasan disiplinnya: flag -t yang akrab dari sesi interaktif adalah racun bagi pipe. Saat -t aktif, daemon menyiapkan pseudo-TTY dan seluruh output proses melewati terminal semu itu — kernel mengubah tiap \n menjadi \r\n, lebar baris dipotong mengikuti kolom terminal, dan kadang kode warna ikut terselip. Dump yang dihasilkan docker exec -t db pg_dump ... terlihat normal di mata, tapi setiap barisnya membawa \r yang membuat psql menolak atau salah membaca saat restore.
Pola yang benar ringkasnya:
| Bentuk perintah | TTY? | Cocok untuk |
|---|---|---|
docker exec db pg_dump ... | gzip | tidak | pipe dan redirect — bentuk backup kita |
docker exec -it db psql | ya | sesi interaktif manusia |
docker exec -i db psql < berkas.sql | tidak | mengalirkan stdin — restore, chapter berikutnya |
docker compose exec -T db pg_dump ... | dimatikan eksplisit | pipe di skrip dan CI |
Baris terakhir layak dihafal: docker compose exec mengalokasikan TTY secara bawaan saat terminalnya interaktif, sehingga flag -T eksplisit menjadi penyelamat di cron dan CI — ingatan jari yang mengetik -it otomatis harus ditumpulkan setiap kali output hendak dipipakan. Kita sudah memakai docker exec -i untuk mengalirkan stdin pada Chapter Migrasi Data Container, dan chapter berikutnya menjadikan pola itu pemandu utama.
Otomatisasi: cron dan Rotasi Arsip #
Backup yang mengandalkan ingatan manusia hanya sekuat ingatan itu. Otomasi paling sederhana di mesin Linux adalah cron — penjadwal bawaan yang menjalankan baris perintah pada menit yang kita tetapkan:
$ mkdir -p /home/budi/backup
$ crontab -eDua baris berikut masuk ke berkas crontab Budi:
0 2 * * * docker exec db pg_dump -U katalog katalog | gzip > /home/budi/backup/katalog-$(date +\%F).sql.gz
30 2 * * * find /home/budi/backup -name 'katalog-*.sql.gz' -mtime +7 -deleteMari kita telaah detail-detail yang menyimpan jebakan:
\%bukan salah tik — cron menafsirkan karakter%sebagai akhir baris perintah; sisanya dianggap masukan stdin. Tanpa escape,$(date +%F)terpotong dan nama berkas berubah kacau —\%adalah satu-satunya cara tanggal tetap tercetak.katalog-$(date +\%F).sql.gz— menghasilkan nama bertanggal sepertikatalog-2026-10-14.sql.gz; tiap malam melahirkan berkas baru sehingga salinan kemarin tidak tertimpa.find -mtime +7 -delete— rotasi otomatis: berkas yang umurnya lebih dari tujuh hari dihapus; jendela seminggu itu penuh dengan tujuh salinan harian sebelum yang tertua pergi.- cron berjalan sebagai user budi — bukan root; akses ke socket daemon mengikuti keanggotaan grup docker yang kita atur pada Chapter Mengatur Akses Docker, dan user mana pun tanpa grup itu akan membangunkan galat permission di log cron, bukan di terminal.
- dua pekerjaan dipisah dua jadwal — backup pukul 02:00, rotasi pukul 02:30; jarak setengah jam itu memberi dump besar waktu untuk selesai sebelum umurnya dihitung ulang.
Arsip di satu mesin masih satu titik kegagalan — disk yang membawa volume juga membawa foldernya. Langkah pamungkasnya adalah mengirim salinan keluar host lewat rsync atau penyimpanan objek, tulang punggung aturan 3-2-1: tiga salinan data, di dua jenis media, satu di luar lokasi — disiplin yang kita bangun lengkap dengan strategi retensi dan pemulihan bencana pada Chapter Backup dan Recovery di Section Deployment dan Production. Malam ini empat bentuk backup sudah berdiri di host kita:
| Perintah inti | Keluaran | Kegunaan |
|---|---|---|
docker exec db pg_dump -U katalog katalog > backup-katalog.sql | SQL teks, 18K | dibaca manusia, dipulihkan lewat psql |
docker exec db pg_dump -U katalog katalog | gzip > backup-katalog.sql.gz | gzip, 4.0K | arsip harian yang ramping |
docker exec db pg_dump -U katalog -Fc katalog > backup-katalog.dump | kustom, 8.0K | restore selektif dan paralel lewat pg_restore |
docker exec db-mysql sh -c 'exec mysqldump -ukatalog -prahasia123 katalog' > backup-katalog-mysql.sql | SQL teks, 12K | padanan MySQL, memuat DROP TABLE IF EXISTS |
Empat berkas itu kini tergeletak tenang di ~/katalog — tetapi setiap berkas backup pada dasarnya adalah janji tertulis yang belum diuji, dan janji database paling sering gagal justru saat ditagih: pada malam pemulihan. Chapter Restore Database menagih janji-janji itu satu per satu, membedah arah data yang berlawanan dengan backup, dan menutupnya dengan ritual disaster drill yang membuktikan database bisa lahir kembali dari satu berkas di folder proyek.
Referensi #
- PostgreSQL — Docker Hub — catatan image resmi soal klien pg_dump di dalam container dan mekanisme initdb.
- MySQL — Docker Hub
— pola backup resmi
sh -c 'exec mysqldump ...'beserta variabel MYSQL_PWD. - PostgreSQL Documentation — rujukan pg_dump, format keluaran, dan konsistensi snapshot MVCC.
- docker container exec — CLI Reference
— flag
-i,-t, dan-euntuk arah stdout/stdin yang benar.