Sertifikat TLS telah terpasang di proxy, perpanjangan berjalan otomatis lewat loop certbot dan nginx -s reload setiap enam jam, dan toko kini melayani pengunjung lewat HTTP/2 di port 443 — data yang melintas jaringan sudah terenkripsi rapi. Namun enkripsi jaringan hanya menutup separuh jalur hidup data: begitu permintaan usai, seluruh kebenaran toko — pesanan, pelanggan, stok, gambar produk — beristirahat di sebuah volume di disk server, dan di sanalah musibah yang lebih sunyi menunggu. Disk yang penuh mendadak bisa membawa turun sistem operasi ikut sekarat, partisi yang sama dengan /var/log bisa menenggelamkan database bersama log yang tak terbatas, dan satu perintah pembersih yang salah waktu mampu memusnahkan volume tanpa sidang. Chapter ini memilih rumah yang benar bagi data produksi: named volume berlabel yang dikelola di luar siklus Compose, volume driver jaringan untuk data yang harus bertahan dari host, dan data-root yang memisahkan seluruh isi Docker ke disk khusus — ditutup dengan pemantauan disk dan kerangka rencana pemulihan bencana.
Tiga Rumah untuk Data Produksi #
Docker menyediakan tiga tempat berbeda untuk data yang harus bertahan, dan ketiganya sudah kita kenal sepanjang Section Penyimpanan Data — yang berubah di produksi hanyalah bobot konsekuensinya. Tabel berikut membandingkan ketiganya dari sudut pandang operator server, bukan lagi sudut pandang developer laptop:
| Pilihan | Di mana data hidup | Kelebihan | Risiko utama |
|---|---|---|---|
| Named volume lokal | di bawah data-root daemon, dikelola penuh oleh Docker | cepat, di-copy-up dengan kepemilikan image, tak terlihat tool host | satu disk dengan sistem operasi — kegagalan disk memusnahkan data |
| Bind mount | direktori host yang kita pilih sendiri | terlihat dan bisa dibaca tool backup host biasa | jebakan UID/GID antara container dan host; berada di luar kendali daemon; mudah tersapu perintah rm -rf yang salah alamat |
| Volume driver jaringan (NFS/EFS) | penyimpanan luar yang dipasang lewat driver local dengan opsi mount | data bertahan dari kehilangan host; bisa dibagi lintas host | latensi jaringan pada setiap tulis; server penyimpanan itu sendiri titik kegagalan; semantik fsync database perlu diuji |
Ketiga baris itu bukan peringkat melainkan peta: pilihan yang tepat bergantung pada jenis beban kerja, dan kita akan menutup chapter dengan tabel keputusan yang memetakan beban kerja ke rumahnya. Sebagai titik berangkat, stack toko membutuhkan dua volume: toko_db-data untuk cluster PostgreSQL di service db — promosi dari volume db-data yang sejauh ini dikelola Compose — dan toko_uploads, rumah baru untuk gambar produk yang diunggah admin lalu disajikan service frontend dari path /uploads/. Keduanya akan kita kelola sebagai named volume lokal berlabel.
Named Volume Berlabel sebagai Standar #
Sampai chapter ini, volume produksi toko lahir otomatis saat docker compose up pertama kali: kunci db-data pada compose.yaml dari Chapter Konfigurasi Production menghasilkan volume bernama toko-produksi_db-data — prefiks project toko-produksi menempel otomatis — dan Compose memusnahkannya pada docker compose down -v. Di produksi, siklus hidup data tidak boleh sama dengan siklus hidup stack: container boleh lahir mati setiap rilis, data harus hidup lebih lama dari semuanya. Promosinya berjalan tiga langkah: ciptakan volume berlabel secara manual, pindahkan isi cluster sekali lewat pipa tar antar volume — pola yang kita latih pada Chapter Migrasi Data Container — lalu nyatakan kedua volume sebagai external di compose.yaml.
$ docker volume create --label env=prod toko_db-data
$ docker volume create --label env=prod toko_uploads
$ docker compose stop db
$ docker run --rm \
-v toko-produksi_db-data:/dari:ro \
-v toko_db-data:/ke \
alpine:3.20 \
sh -c 'cd /dari && tar cf - . | (cd /ke && tar xf -)'Perintah salinan antar volume itu selesai tanpa satu baris output pun — tar hanya bersuara ketika ada yang gagal. Langkah terakhir promosi menunggu di compose.yaml: deklarasi volume berganti bentuk, lalu db menyala menempel rumah barunya:
services:
db:
image: postgres:18-alpine
ports:
- "127.0.0.1:5432:5432"
volumes:
- db-data:/var/lib/postgresql/data
frontend:
image: nginx:1.29-alpine
volumes:
- uploads:/usr/share/nginx/html/uploads
volumes:
db-data:
external: true
name: toko_db-data
uploads:
external: true
name: toko_uploadsMari kita telaah tiga baris kunci pada deklarasi tersebut:
external: true— Compose dilarang menciptakan maupun menghapus volume ini;docker compose down -v, yang biasanya memusnahkan volume milik project, melewati volume eksternal tanpa menyentuhnya. Volume hidup di luar siklusup/downpersis seperti yang kita inginkan untuk data produksi.name: toko_db-data— nama sebenarnya dipatok secara eksplisit tanpa prefiks project; nama lamatoko-produksi_db-datasengaja tidak dipertahankan agar volume produksi tidak pernah tertukar dengan volume internal project lain yang kebetulan memakai kunci sama.- Kunci
db-datatetap pendek di sisi service — kunci itu hanya pengenal lokal di dalam berkas;dbtetap menulisdb-data:/var/lib/postgresql/datasementara dunia luar mengenaltoko_db-data. Pemisahan nama-pendek-dalam-berkas dan nama-penuh-di-dunia inilah yang membuat stack yang sama bisa dipakai ulang di project lain tanpa tabrakan nama volume.
Satu baris baru pada service db di fragment di atas juga pantas dijelaskan: pemetaan "127.0.0.1:5432:5432" adalah pemetaan debug yang terikat loopback — pola Chapter Port Publishing — yang Budi tanam saat migrasi volume agar psql dari host bisa memeriksa data tanpa membuka satu pintu pun ke jaringan. Kebiasaan itu kita verifikasi ulang lewat pendengar kernel pada Chapter Docker di VPS. Sekarang db dinyalakan ulang dengan deklarasi baru, dan isi cluster dibuktikan utuh:
$ docker compose up -d db && docker compose exec db psql -U toko -d toko -c "SELECT count(*) FROM produk;"Output yang Dihasilkan:
[+] Running 2/2
✔ Container toko-produksi-db-1 Recreated 0.4s
✔ Container toko-produksi-db-1 Healthy 3.1s
count
-------
18
(1 row)Recreated terjadi karena pemetaan volume db berganti, dan delapan belas baris produk pindah rumah tanpa kehilangan satu baris pun — volume lama toko-produksi_db-data kini boleh dibongkar lewat docker volume rm begitu kita yakin, sedangkan toko_uploads lahir kosong menunggu unggahan pertama admin.
Label yang kita tempelkan saat penciptaan tidak sekadar hiasan metadata — label adalah bukti yang bisa diperiksa dan pagar untuk perintah pembersihan. Bukti itu terbaca langsung dari inspect:
$ docker volume inspect toko_db-dataOutput yang Dihasilkan:
[
{
"CreatedAt": "2026-10-22T09:12:04+07:00",
"Driver": "local",
"Labels": {
"env": "prod"
},
"Mountpoint": "/data/docker/volumes/toko_db-data/_data",
"Name": "toko_db-data",
"Options": {},
"Scope": "local"
}
]Mari kita telaah dua field pada keluaran tersebut:
Labelsberisi"env": "prod"— satu-satunya label yang melekat, karena volume ini lahir dari perintah manual kita, bukan dari Compose (labelcom.docker.compose.*hanya diberikan kepada volume yang diciptakan Compose); inilah tanda tangan bahwa volume dikelola manusia di luar siklus stack.Mountpointberada di bawah/data/docker— path itu bukan bawaan/var/lib/docker; server ini memakai data-root di disk terpisah, keputusan yang kita pasang pada bagian akhir chapter ini.
Ujian sebenarnya dari external: true adalah perintah yang paling ditakuti operator. Budi membuktikannya sekali pada jendela pemeliharaan dini hari, ketika toko sepi: stack dimatikan total dengan -v, lalu dinyalakan ulang.
$ docker compose down -vOutput yang Dihasilkan (dipotong):
[+] Running 7/7
✔ Container toko-produksi-proxy-1 Removed
✔ Container toko-produksi-db-1 Removed
...
✔ Network toko-produksi_backend-net Removed
✔ Network toko-produksi_frontend-net RemovedTidak ada satu baris Volume ... Removed pada keluaran itu — satu-satunya baris yang biasanya memusnahkan data justru absen, karena kedua volume kita berstatus eksternal. Baris berikutnya adalah pembuktian pamungkas:
$ docker volume ls --filter label=env=prodOutput yang Dihasilkan:
DRIVER VOLUME NAME
local toko_db-data
local toko_uploadsKedua volume masih utuh, dan docker compose up -d menuntaskan giliran terakhirnya: db Healthy dalam belasan detik dan seluruh data pesanan kembali terbaca — total pemadaman tercatat 47 detik pada jendela yang memang sengaja dipilih.
Volume Driver Jaringan: Data di Luar Host #
Named volume lokal menyelesaikan masalah kepemilikan dan siklus hidup, tetapi data tetap terkurung di satu disk host. Docker menawarkan jalan keluar yang mengejutkan murahnya: driver local — driver yang sama dengan named volume biasa — dapat memasang penyimpanan jaringan lewat opsi mount yang diteruskan ke kernel. Bentuk lengkapnya untuk NFS:
$ docker volume create --driver local \
--opt type=nfs \
--opt device=:/export/db \
--opt o=addr=192.168.1.30,nfsvers=4 \
vol-db-nfsOutput yang Dihasilkan:
vol-db-nfsMari kita telaah tiga pasang opsi tersebut:
--opt type=nfs— jenis filesystem yang akan dipasang kernel; driverlocalpada dasarnya membungkus panggilanmount, sehingga tipe yang didukung adalah tipe yang didukung kernel host — nfs, tmpfs, bahkannonedengan opsi bind.--opt device=:/export/db— sumber mount dari sisi server NFS; titik dua di depan adalah sintaks klasik NFSserver:path, dan karena alamat server sudah berada di opsio, device cukup berisi path ekspornya.--opt o=addr=192.168.1.30,nfsvers=4— opsi mount tambahan: alamat server NFS di192.168.1.30dan versi protokol 4; keduanya diteruskan mentah ke kernel saat volume pertama kali dipakai sebuah container.
Volume hasil perintah ini tampak seperti volume biasa dari sisi container — nama service tetap menulis vol-db-nfs:/var/lib/postgresql/data — bedahnya terlihat pada inspect, di mana field Options kini terisi penuh:
$ docker volume inspect vol-db-nfs --format '{{json .Options}}'Output yang Dihasilkan:
{"device":":/export/db","o":"addr=192.168.1.30,nfsvers=4","type":"nfs"}Uji cepat membuktikan data benar-benar menyeberang ke server NFS: sebuah container menulis berkas ke volume, lalu berkas yang sama terbaca dari sisi server.
$ docker run --rm -v vol-db-nfs:/data alpine:3.20 sh -c 'echo uji > /data/uji-nfs.txt'
$ ssh [email protected] cat /export/db/uji-nfs.txtOutput yang Dihasilkan:
ujiSekarang bagian yang jujur. Memindahkan volume ke NFS tidak memindahkan kita keluar dari dunia titik kegagalan tunggal — ia hanya memindahkan titik itu dari disk VPS ke server NFS; NAS yang mati kini membawa serta seluruh volume yang bergantung padanya, ditambah latensi jaringan yang menempel pada setiap penulisan. Untuk database transaksional, tambahan satu lapis lagi yang harus diuji: semantik fsync di atas NFS harus dijamin benar oleh server dan konfigurasinya, atau integritas WAL PostgreSQL bisa terancam. Karena itu keputusan toko kita: cluster PostgreSQL tetap di named volume lokal, dan keselamatannya dibangun lewat backup logis malaman — bukan lewat media penyimpanan. NFS masuk hitungan justru untuk berkas unggahan yang harus dibagi lintas host, dan SAN atau volume cloud yang menempel ke satu host dalam blok tebal menjadi masuk akal ketika latensi penuh jaringan tak lagi bisa ditoleransi — wilayah yang kita jelajahi lebih dalam saat stack tumbuh melebihi satu mesin.
Data-root di Disk Terpisah dari Sistem Operasi #
Pilihan ketiga tidak menyangkut volume sama sekali, melainkan rumah besar tempat seluruh isi Docker tinggal: image, container, build cache, dan seluruh volume lokal hidup di bawah data-root — /var/lib/docker secara bawaan, di partisi yang sama dengan sistem operasi. Konsekuensinya berlapis: partisi / yang penuh oleh layer image membuat sshd gagal menulis log dan operator kehilangan akses tepat saat dibutuhkan, ukuran konsumsi Docker tercampur dengan noise sistem, dan rebuild OS mesti hati-hati agar /var/lib/docker tidak ikut disapu. Server produksi toko punya disk data kedua /dev/vdb1; kita pasang sebagai /data lewat fstab agar bertahan lintas boot:
$ sudo mkfs.ext4 /dev/vdb1
$ sudo mkdir -p /data
$ echo '/dev/vdb1 /data ext4 defaults,nofail 0 2' | sudo tee -a /etc/fstab
$ sudo mount -a
$ df -h / /dataOutput yang Dihasilkan:
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 20G 6.2G 13G 31% /
/dev/vdb1 100G 4.2G 91G 5% /dataSatu baris pada fstab menuntaskan pemasangan; opsi nofail menjaga server tetap boot meski disk data lepas. Selanjutnya daemon.json — berkas yang kita kenal dari Chapter Konfigurasi Docker Engine — mengarahkan Docker ke rumah barunya:
{
"data-root": "/data/docker"
}$ sudo systemctl restart docker
$ docker infoOutput yang Dihasilkan (dipotong):
Server:
Images: 5
Server Version: 29.9.0
Storage Driver: overlayfs
driver-type: containerd
...
Docker Root Dir: /data/docker
...Baris Docker Root Dir: /data/docker adalah bukti pindahan berhasil, dan seluruh Mountpoint pada docker volume inspect kini berprefiks /data/docker/volumes/. Pada server yang sudah lama berjalan, pindahan data-root menuntut satu langkah pengaman:
Tiga keuntungan pemisahan ini terasa sejak hari pertama. Pertama, disk data yang penuh tidak lagi membunuh sistem operasi — sshd, jurnal sistem, dan ufw tetap hidup sehingga operator bisa masuk dan mendiagnosis persis pada saat darurat. Kedua, konsumsi Docker menjadi terukur dengan satu perintah: df -h /data kini adalah tagihan Docker dan hanya tagihan Docker. Ketiga, sistem operasi bisa dibangun ulang dari nol tanpa menyentuh data — cukup lepas partisi / lalu pasang ulang, dan fstab yang sama mengembalikan /data lengkap dengan seluruh volume produksi.
Label sebagai Pagar saat Membersihkan #
Label env=prod yang kita tempelkan pada kedua volume kini menunaikan tugas keduanya: menjadi pagar pada saat operator membersihkan mesin. Penyaring pertama memangkas daftar volume menjadi hanya milik produksi:
$ docker volume ls --filter label=env=prodOutput yang Dihasilkan:
DRIVER VOLUME NAME
local toko_db-data
local toko_uploadsPenyaring yang lebih penting justru bentuk negasinya, karena perintah pembersih volume menghapus semua volume yang tidak dipakai container — dan volume produksi yang sedang dipakai db aman, tetapi volume produksi yang sesaat tidak terpasang (misalnya saat stack sedang down di jendela pemeliharaan) berada tepat di jalur tembak.
Memantau Disk dan Menyiapkan Musibah #
Rumah yang benar tetap perlu diawasi isinya. Satu perintah merangkum seluruh konsumsi disk milik daemon dalam empat baris:
$ docker system dfOutput yang Dihasilkan:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 5 5 872MB 0B (0%)
Containers 5 5 36.7MB 0B (0%)
Local Volumes 2 2 412MB 0B (0%)
Build Cache 0 0 0B 0BMari kita telaah empat baris tersebut dari sudut pandang produksi:
Images5/5 aktif — lima image stack toko semuanya terpakai;RECLAIMABLE 0Bberarti tidak ada image menganggur, wajar pada server yang hanya menarik image yang dideklarasikan compose.yaml.Containers36.7MB — ukuran ini adalah writable layer ditambah berkas log yang masih tersimpan; angka yang terkunci kecilnya adalah buah rotasi log yang kita pasang pada chapter berikutnya.Local Volumes412MB — cluster PostgreSQL ditambah arsip unggahan; satu-satunya baris yang tumbuh seiring usia toko, dan oleh karena itu baris yang paling perlu diawasi.Build Cache0 — server produksi tidak pernah membangun image; seluruh image datang dari registry bertag, keputusan yang kita kunci pada Chapter Konfigurasi Production.
Angka-angka itu melengkapi df -h /data: yang pertama menjelaskan isi, yang kedua mengukur tempat. Aturan praktis toko menetapkan ambang waspada 80% — pelebihan ambang memicu notifikasi yang mekanismenya kita bangun lengkap dengan skrip dan cron pada Chapter Docker di VPS, sementara pembahasan penuh pemantauan resource CPU dan memori berada di Chapter Monitoring Resource. Pemantauan adalah separuh kesiapan; separuh lainnya adalah pengakuan jujur bahwa musibah tetap mungkin, dan untuk itu empat kerangka disaster recovery plan kita pancangkan sekarang:
- Definisikan musibah yang ditanggung — disk mati, host mati total, data terkorupsi, dan human error seperti
docker volume rmyang salah sasaran; keempatnya menuntut jawaban berbeda, dan rencana yang hanya menjawab satu adalah rencana setengah jadi. - Salinan mengikuti pola 3-3-1 — tiga salinan di dua media berbeda dengan satu di luar lokasi; di dunia Docker, wujudnya adalah volume hidup plus arsip tar plus dump SQL, kerangka yang kita bangun penuh pada Chapter Backup dan Recovery.
- Jalur pemulihan tertulis dan teruji — runbook bernomor yang dilatih berkala, bukan dokumen yang hanya dibaca saat panik; pola helper container untuk arsip volume berasal dari Chapter Backup dan Restore Volume, dan pola dump logis dari Chapter Backup Database.
- Angka RTO dan RPO disepakati sebelum musibah — berapa lama boleh mati dan berapa banyak data yang boleh hilang adalah keputusan bisnis, bukan hasil hitungan panik di tengah insiden.
Seluruh pilihan chapter ini kini bisa dirangkum menjadi satu tabel keputusan — pemetaan beban kerja ke rumah yang tepat:
| Beban kerja | Rumah yang tepat | Alasan singkat |
|---|---|---|
| Database transaksional satu host | named volume lokal + dump logis terjadwal | latensi disk lokal; keselamatan dibangun dari backup, bukan dari media |
| Berkas unggahan pengguna | named volume lokal; NFS bila harus dibagi lintas host | berkas statis aman diarsipkan dengan tar sambil layanan hidup |
| Konfigurasi statis | configs Compose atau bind mount :ro | bukan data — cukup dibaca, tidak perlu bertahan |
| Data sementara (sesi, berkas antara) | tmpfs mount | hilang bersama container memang perilaku yang diinginkan |
Data toko kini punya rumah yang benar: volume berlabel yang tak tersentuh siklus stack, data-root di disk terpisah dari sistem operasi, dan kerangka pemulihan yang menunggu dilengkapi. Namun ada satu penghuni disk yang tumbuh diam-diam lebih rajin dari data mana pun dan baru terasa saat semuanya terlambat — log. Driver bawaan json-file menulis tanpa batas kecuali kita mengaturnya, dan pengaturan itu, beserta log terstruktur dan korelasi antar service, adalah panggung Chapter Logging Production.
Referensi #
- Docker storage — perilaku volume, bind mount, dan data-root pada dokumentasi resmi.
- docker volume
— referensi
volume createdengan label serta opsi driver untuk NFS. - Konfigurasi daemon Docker
— kunci
data-rootdan seluruh opsi daemon.json. - docker system df — ringkasan konsumsi disk image, container, dan volume.