Container dirancang sekali pakai: docker rm menghapus container beserta seluruh tulisannya, dan itu justru fitur — deployment berikutnya mulai dari keadaan bersih. Masalahnya, database yang datanya ikut lenyap bukan fitur melainkan bencana. Chapter ini membedah cara kerja penyimpanan container — layer image yang read-only, writable layer, dan copy-on-write — lalu tiga jalan menyelamatkan data keluar dari container: volume, bind mount, dan tmpfs. Karena docker tidak terpasang di lingkungan latihan, mekanismenya kita simulasikan di filesystem lokal, lengkap dengan fixture JSON dan latihan backup yang sesungguhnya dijalankan.
Layer Image dan Copy-on-Write #
Image adalah tumpukan layer read-only — satu layer per instruksi Dockerfile, dan layer-layer itu dipakai bersama oleh semua container dari image yang sama. Saat container berjalan, satu writable layer tipis ditumpuk di puncaknya. Proses di dalam container melihat gabungan seluruh tumpukan sebagai satu filesystem utuh; ketika proses menulis ke file milik layer bawah, file itu disalin dulu ke writable layer (copy-up), lalu ditulis di sana. Layer image tidak pernah tersentuh. Simulasinya di filesystem biasa:
mkdir -p image/app writable
printf 'versi=1.0\n' > image/app/app.conf
printf 'kunci=r4h451a\n' > image/app/secret.key
echo '== layer image (read-only) =='
cat image/app/app.conf
echo '== proses menulis app.conf: disalin dulu ke writable layer =='
cp image/app/app.conf writable/app.conf
printf 'versi=1.1\n' > writable/app.conf
cat writable/app.conf
echo '== tampilan gabungan yang dilihat proses di dalam container =='
for f in image/app/*; do
nama=$(basename "$f")
if [ -f "writable/$nama" ]; then cat "writable/$nama"; else cat "$f"; fi
doneOutput yang Dihasilkan:
== layer image (read-only) ==
versi=1.0
== proses menulis app.conf: disalin dulu ke writable layer ==
versi=1.1
== tampilan gabungan yang dilihat proses di dalam container ==
versi=1.1
kunci=r4h451aMari kita bedah bloknya:
- Dua direktori = dua layer:
image/appberisi file bawaan image — konfigurasi versi 1.0 dan satu secret key. cpadalah momen copy-up: file disalin ke writable layer sebelum diubah — persis yang dilakukan kernel saat proses menulis file dari layer bawah.catpertama: layer image terbukti tak tersentuh — isinya masihversi=1.0setelah container menulisversi=1.1.- Loop
for f in image/app/*: mensimulasikan tampilan gabungan — file yang ada di writable layer menang, file yang tidak ada jatuh ke layer image.
Konsekuensinya lugas: docker rm membuang writable layer, dan semua perubahan sejak container menyala ikut lenyap — config, log, data. Image tetap bersih dan siap dipakai container baru. Data yang harus bertahan (isi database, unggahan user) wajib pindah keluar dari tumpukan itu.
Tiga Jalan Keluar untuk Data #
Tiga mekanisme mount membawa data keluar dari container, masing-masing dengan wilayah kerjanya:
| Mekanisme | Lokasi sebenarnya | Dikelola | Bertahan setelah container dihapus | Kasus klasik |
|---|---|---|---|---|
| volume | /var/lib/docker/volumes/... | engine container | ya | data database |
| bind mount | path host mana pun | kita sendiri | ya (ini file host) | konfigurasi, source code |
| tmpfs | memori | kernel | tidak — memang begitu | secret, state sementara |
Sintaksnya satu keluarga: -v data-db:/var/lib/postgresql/data untuk volume, -v /home/budi/conf:/etc/nginx/conf.d:ro untuk bind mount, --tmpfs /run untuk memori. Akhiran :ro memasang baca-saja — container bisa membaca tapi tidak menulis; :rw adalah kebalikannya.
Volume dari Dalam #
Volume adalah wadah milik engine container: dibuat dengan docker volume create data-db, dipasang ke container, dan isinya hidup di /var/lib/docker/volumes/ di host. Satu volume bisa dipakai bergantian oleh banyak container, dan datanya tidak tersentuh siklus hidup container mana pun. Berkas volume.json di bundle materi adalah hasil docker volume inspect pada volume buatan compose:
jq -r '.[0].Name, .[0].Mountpoint' volume.json
echo '== asal-usulnya (label compose) =='
jq -r '.[0].Labels["com.docker.compose.project"]' volume.jsonOutput yang Dihasilkan:
toko_data-db
/var/lib/docker/volumes/toko_data-db/_data
== asal-usulnya (label compose) ==
tokoTiga field itu bercerita: nama toko_data-db adalah gabungan nama proyek compose (toko) dan nama volume di compose file (data-db) — persis pola prefix yang kita lihat di Chapter Docker Compose
; Mountpoint adalah lokasi sebenarnya di host tempat data menetap; dan label menyatakan volume itu dibuat otomatis oleh compose proyek toko, bukan manual.
Bind Mount: Dua Path Satu Data #
Bind mount menempelkan path host apa adanya ke path di dalam container — tanpa perantara, tanpa salinan. Editor di host menyunting file yang persis sama dengan yang dibaca proses di dalam container; begitulah hot reload bekerja. Efek “dua path menunjuk satu data” bisa dirasakan lokal lewat analogi symlink:
mkdir -p host-data aplikasi
printf 'port=8080\n' > host-data/app.conf
ln -s ../host-data/app.conf aplikasi/app.conf
echo '== dibaca lewat path "container" =='
cat aplikasi/app.conf
echo '== host mengubah isi file =='
printf 'port=9090\n' > host-data/app.conf
cat aplikasi/app.confOutput yang Dihasilkan:
== dibaca lewat path "container" ==
port=8080
== host mengubah isi file ==
port=9090Bedah singkatnya: ln -s membuat jendela kedua menuju file yang sama, jadi penulisan lewat host-data/app.conf langsung terlihat dari aplikasi/app.conf tanpa menyalin ulang apa pun. Batas analoginya perlu jujur diakui — symlink adalah redirect path yang terlihat lewat readlink, sedangkan bind mount sungguhan adalah mount entry di mount namespace kernel yang tidak kelihatan dari dalam container. Efek praktisnya sama: satu data, dua jendela. Satu perbedaan yang sering menggigit: file bind mount dimiliki oleh UID di host, jadi container yang berjalan sebagai user berbeda bisa saja tidak punya izin menulisnya — topik yang menyambung langsung ke chapter berikutnya.
Backup dan Restore #
Volume berumur panjang, tetapi tetap terikat pada satu mesin. Backup memindahkan isinya ke berkas yang bisa disalin ke mana saja, dan polanya adalah tar: arsipkan isi volume, simpan hasilnya di host. Latihan berikut menjalankan pola itu sungguhan di folder lokal:
mkdir -p data-lama backup
printf 'pelanggan=budi\n' > data-lama/pelanggan.csv
printf 'pesanan=1024\n' > data-lama/pesanan.csv
tar -C data-lama -czf backup/data-db.tar.gz .
echo '== isi arsip =='
tar -tzf backup/data-db.tar.gz | sort
mkdir -p data-baru
tar -C data-baru -xzf backup/data-db.tar.gz
diff -r data-lama data-baru && echo 'restore identik dengan aslinya'Output yang Dihasilkan:
== isi arsip ==
./
./pelanggan.csv
./pesanan.csv
restore identik dengan aslinyaEmpat langkahnya:
tar -C data-lama -czf ... .: pindah direktori lebih dulu (-C) supaya path di dalam arsip bersih —./pelanggan.csv, bukandata-lama/pelanggan.csv;.berarti seluruh isi volume.tar -tzf: melisting isi arsip terkompresi — inventaris sebelum menyimpannya.tar -C data-baru -xzf: ekstraksi ke lokasi baru — restore.diff -r: membandingkan dua pohon direktori; sunyi diikuti pesan sukses berarti restore menghasilkan salinan identik.
Di mesin berdocker, pola yang sama berjalan lewat container sementara yang memasang volume lalu mengarsipkannya: docker run --rm -v toko_data-db:/data -v "$PWD:/backup" debian:12-slim tar -C /data -czf /backup/data-db.tar.gz . — volume dipasang, diarsipkan, container pergi.
Data kini bertahan melewati hidup-mati container. Satu dimensi terakhir sebelum menutup section: container yang selama ini kita jalankan — seberapa aman sebenarnya ia berjalan? Chapter Container Security membedah user non-root, penanganan secret, dan scanning.
Referensi lanjut: dokumentasi storage Docker untuk perilaku volume dan bind mount secara lengkap.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
volume.json
Unduh berkas[
{
"Name": "toko_data-db",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/toko_data-db/_data",
"Labels": {
"com.docker.compose.project": "toko",
"com.docker.compose.volume": "data-db"
},
"Scope": "local"
}
]