Kerangka pemilihan pada Chapter Konsep Persistence berakhir dengan satu saran: bila ragu, mulailah dari volume. Sekarang saatnya membuktikan bahwa anjuran itu berdiri di atas mekanisme nyata, bukan sekadar selera. Chapter ini membedah siklus hidup volume penuh — membuatnya, memeriksa lokasi fisiknya, memasangnya ke container, lalu membawa container itu ke penghujung umur dan menyaksikan datanya tetap berdiri. Kita juga membereskan utang kecil dari chapter sebelumnya: dua volume anonim yang ditinggalkan container postgres Budi, yang diam-diam masih memakan ruang disk.

Membuat dan Memeriksa Volume #

Volume dikelola sebagai objek first-class, setara dengan container dan image — ia punya kelompok perintah docker volume sendiri. Membuatnya tidak butuh container sama sekali:

bash
$ docker volume create data-web

Output yang Dihasilkan:

text
data-web

Output satu baris itu adalah nama volume yang dikembalikan daemon sebagai tanda keberhasilan. Daftar seluruh volume di mesin kini dapat kita baca:

bash
$ docker volume ls

Output yang Dihasilkan:

text
DRIVER    VOLUME NAME
local     7fdc9b6e34d4aae50353e229b751b7575e42325338f3c76f95a359759135b772
local     800dbb7e18afac9956c38e55c4fdb1e7358a4c95c9cfe9d28bce86e3dd2ff9e2
local     data-web
local     latihan-volume

Mari kita telaah empat baris di atas:

  1. DRIVER bernilai local — setiap volume dilayani oleh sebuah driver penyimpanan; local adalah driver bawaan yang menyimpan data di disk host tempat daemon berjalan.
  2. Dua baris nama heksadesimal 64 karakter — volume anonim, masing-masing lahir diam-diam ketika Budi menjalankan container postgres dua kali pada Chapter *Konsep Persistence`; datanya masih ada di sana, hanya tidak ada container yang memakainya lagi.
  3. data-web — volume bernama yang baru saja kita buat; namanya ditentukan manusia, sehingga bisa dirujuk kapan pun tanpa menyalin deretan hex.
  4. latihan-volume — kenalan lama dari Chapter Struktur Perintah Docker, bukti bahwa volume yang tak terpakai pun tidak terbuang sendiri: volume memang hanya hilang bila dihapus secara eksplisit.

Metadata lengkap sebuah volume bisa kita baca dengan docker volume inspect:

bash
$ docker volume inspect data-web

Output yang Dihasilkan:

json
[
    {
        "CreatedAt": "2026-10-13T12:07:44Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/data-web/_data",
        "Name": "data-web",
        "Options": null,
        "Scope": "local"
    }
]

Mari kita telaah field-field pentingnya:

  1. CreatedAt — waktu volume diciptakan, lengkap dengan zona waktu UTC; ini waktunya perintah docker volume create tadi dijalankan.
  2. Driver: local — driver yang sama dari kolom pertama tabel docker volume ls, kini tercatat sebagai metadata resmi.
  3. Labels: null — volume ini dibuat tanpa label; flag --label pada docker volume create bisa menempelkan pasangan key-value untuk kebutuhan inventarisasi.
  4. Mountpoint — lokasi fisik data di host: /var/lib/docker/volumes/data-web/_data. Inilah jawaban atas pertanyaan “di mana sebenarnya data volume tinggal”.
  5. Scope: local — volume hidup pada satu host Docker saja; lingkup lintas-cluster seperti swarm memakai mekanisme berbeda di luar jalur course ini.

Mountpoint memang berada di filesystem host, tetapi area /var/lib/docker adalah wilayah sang daemon — kita tidak mengedit isinya secara langsung, melainkan memasangnya ke container lewat jalur resminya. Bagaimana bentuk jalur resmi itu? Mari kita pasangkan volume ini ke nginx.

Memasang Volume Bernama ke Container #

Pemasangan volume ke container memakai flag -v yang bagian-bagiannya dipisah titik dua — untuk volume bernama, bagian pertamanya adalah nama volume, bukan path host:

bash
$ docker run -d --name web -p 8080:80 -v data-web:/usr/share/nginx/html nginx

Output yang Dihasilkan:

text
f7c2a91b3e55913c4a5f1c6c0a4d8e2b3f4a5b6c7d8e9f0a1b2c3d4e5f60718a

Sesaat setelah container menyala, kita periksa halaman yang disajikannya:

bash
$ curl localhost:8080

Output yang Dihasilkan:

text
<!doctype html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Halaman selamat datang bawaan nginx masih utuh — padahal /usr/share/nginx/html sudah tertutup volume kosong. Mari kita telaah apa yang sebenarnya terjadi:

  1. -v data-web:/usr/share/nginx/html — bagian pertama adalah nama volume, bagian kedua adalah path tujuan di dalam container; container kini menulis dan membaca path itu pada volume, bukan pada writable layer.
  2. Volume kosong diisi lebih dulu — ketika sebuah volume masih kosong dipasang pada path yang berisi berkas milik image, daemon menyalin berkas-berkas itu ke dalam volume sebelum container menyala; index.html dan 50x.html bawaan nginx kini menjadi isi awal data-web.
  3. curl tetap menampilkan halaman bawaan — karena pra-isi tadi, nginx menyajikan berkas yang sama seolah tidak ada mount sama sekali.

Bentuk pendek -v punya saudara panjang --mount yang menuliskan setiap bagian secara eksplisit:

Aspek-v singkat--mount panjang
Contoh-v data-web:/usr/share/nginx/html--mount type=volume,src=data-web,dst=/usr/share/nginx/html
Tata tulisTiga bagian dipisah titik duaPasangan key=value dipisah koma
Jenis mountDitebak dari bentuk bagian pertamaDinyatakan gamblang lewat type=volume
Volume belum adaDibuat otomatis oleh daemonNamanya tertulis gamblang sebagai src=...
Paling tepat untukPerintah cepat di terminalSkrip dan dokumentasi yang harus terbaca jelas

Kedua bentuk menunjuk volume yang sama dan saling menggantikan; pilihan di antara keduanya adalah urusan gaya dan kejelasan, bukan fungsi.

Momen Kebenaran: Data yang Selamat dari docker rm #

Inilah demonstrasi inti chapter ini, dan sekaligus balasan langsung atas kehilangan data Budi di chapter sebelumnya. Kita tulis satu berkas ke path yang dipasangi volume:

bash
$ docker exec web sh -c 'echo halo > /usr/share/nginx/html/halo.txt'

Berkas halo.txt kini tinggal di dalam volume data-web. Sekarang kita lakukan hal yang pada Chapter Writable Container Layer menghapus segalanya: buang containernya, lalu jalankan container baru dengan memasang volume yang sama:

bash
$ docker rm -f web
$ docker run -d --name web -p 8080:80 -v data-web:/usr/share/nginx/html nginx
$ docker exec web cat /usr/share/nginx/html/halo.txt

Output yang Dihasilkan:

text
web
f7c2a91b3e55913c4a5f1c6c0a4d8e2b3f4a5b6c7d8e9f0a1b2c3d4e5f60718a
halo

Satu kata halo di baris terakhir adalah seluruh bukti yang dibutuhkan. Mari kita telaah mengapa data itu selamat:

  1. docker rm -f web hanya membuang writable layer — container beserta layer tertulisnya terhapus, tetapi volume adalah objek terpisah yang tidak ikut mati; inilah pemisahan umur yang dijanjikan Chapter Konsep Persistence.
  2. Container baru memasang volume yang sama — -v data-web:/usr/share/nginx/html pada run kedua menunjuk volume bernama yang sama, sehingga isi lama langsung terlihat tanpa proses pemulihan apa pun.
  3. halo.txt terbaca utuh — berkas itu tidak pernah menumpang writable layer; sejak ditulis, rumahnya adalah mountpoint volume di disk host.
  4. Pola ini berulang sebanyak apa pun — container boleh dibuang dan dijalankan puluhan kali; selama nama volumenya sama, data tetap berdiri.

Volume Anonim versus Volume Bernama #

Dua baris hex pada tabel docker volume ls di awal chapter mewakili kelas kedua volume: yang anonim. Volume anonim lahir tanpa diminta lewat tiga pintu — instruksi VOLUME di Dockerfile image (pintu yang mengejutkan Budi pada Chapter VOLUME dan LABEL), flag -v yang hanya membawa satu path tanpa nama, atau pemasangan --mount tanpa src. Tanpa nama, satu-satunya identitasnya adalah deretan 64 karakter hex yang praktis tidak bisa diingat manusia.

AspekVolume anonimVolume bernama
Nama64 karakter heksadesimal acakDitentukan kita, misalnya data-web
Cara lahirInstruksi VOLUME image, -v /path, atau --mount tanpa srcdocker volume create atau -v nama:/path
Dipakai ulangSulit — nama acak sulit dirujuk dan dicatatMudah — cukup sebut namanya di -v atau --mount
Nasib setelah container matiKerap terlantar tanpa pemilik yang menyadarinyaTetap dikelola sebagai objek bernama
Cara membersihkandocker volume prune (anonim tak terpakai)docker volume rm nama saat benar-benar dibuang

Kabar penting berikutnya menyangkut keselamatan volume dari perintah pembersihan. docker image prune hanya menyentuh image; docker system prune bawaan pun tidak membuang volume — flag --volumes harus dituliskan secara eksplisit untuk itu. Sementara itu, docker volume prune memang khusus: sasaran defaultnya hanya volume anonim yang tidak dipakai container mana pun. Kita buktikan sambil membereskan utang Budi:

bash
$ docker volume prune

Output yang Dihasilkan:

text
WARNING! This will remove all anonymous volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
7fdc9b6e34d4aae50353e229b751b7575e42325338f3c76f95a359759135b772
800dbb7e18afac9956c38e55c4fdb1e7358a4c95c9cfe9d28bce86e3dd2ff9e2

Total reclaimed space: 75.6MB

Mari kita telaah hasil pembersihan ini:

  1. Dua volume anonim terbuang — keduanya volume anonim bekas container postgres Budi yang tidak lagi dipakai container mana pun; 75.6 MB ruang disk kembali, termasuk sisa data peserta yang memang sudah tidak berharga.
  2. data-web dan latihan-volume selamat — keduanya volume bernama, sehingga berada di luar jangkauan docker volume prune bawaan; prinsipnya: yang anonim dianggap sampah, yang bernama dianggap milik.
  3. Konfirmasi [y/N] — perintah pembersihan meminta konfirmasi karena tidak bisa dibatalkan; tekan y hanya setelah docker volume ls dibaca ulang.

Volume untuk Database #

Kini kita ulangi kisah Budi dari Chapter Konsep Persistence dengan satu perubahan kecil yang mengubah segalanya: direktori data PostgreSQL dipasang ke volume bernama pgdata. Kali ini kita pakai bentuk panjang --mount untuk latihan membaca gaya eksplisit:

bash
$ docker volume create pgdata
$ docker run -d --name db-latihan --mount type=volume,src=pgdata,dst=/var/lib/postgresql/data -e POSTGRES_PASSWORD=rahasia123 postgres:18-alpine
$ docker exec db-latihan pg_isready -U postgres
$ docker exec db-latihan psql -U postgres -c "CREATE TABLE peserta (nama TEXT); INSERT INTO peserta VALUES ('Budi'), ('Siti');"

Output yang Dihasilkan:

text
pgdata
d3641eb7d82395f49f0077625f0adf08adfccd8a1713158b6e28020b6f533cbf
/var/run/postgresql:5432 - accepting connections
CREATE TABLE
INSERT 0 2

Tabel peserta kembali terisi dua baris. Sekarang momen yang di chapter sebelumnya berakhir musnah — container dibuang total, lalu dibangkitkan dengan volume yang sama:

bash
$ docker rm -f db-latihan
$ docker run -d --name db-latihan --mount type=volume,src=pgdata,dst=/var/lib/postgresql/data -e POSTGRES_PASSWORD=rahasia123 postgres:18-alpine
$ docker exec db-latihan psql -U postgres -c "SELECT * FROM peserta;"

Output yang Dihasilkan:

text
db-latihan
aba83107d4358f6a40022b3ca52c7c7b0787198d7c94a180c5d72118f14490be
 nama
------
 Budi
 Siti
(2 rows)

Mari kita telaah bedanya dengan kejadian sebelumnya:

  1. Direktori data kini beralamat di volume — /var/lib/postgresql/data menunjuk ke pgdata, sehingga initdb berjalan ke dalam volume, bukan writable layer.
  2. Container kedua tidak menjalankan inisialisasi ulang — entrypoint PostgreSQL mendapati direktori data sudah berisi cluster utuh, jadi initdb dilewati dan container siap lebih cepat.
  3. SELECT mengembalikan data lama — tabel peserta beserta isinya selamat melewati docker rm, persis kabar yang gagal kita terima ketika data masih menumpang writable layer.
  4. POSTGRES_PASSWORD diabaikan pada data yang sudah ada — inisialisasi hanya terjadi sekali; variabel lingkungan tadi tidak menimpa cluster yang sudah terbentuk.

Sebelum berpindah topik, kita rapikan arena: docker rm -f web db-latihan menghapus kedua container latihan, sementara volume data-web dan pgdata sengaja kita biarkan hidup — keduanya akan berguna sebagai contoh kenalan pada chapter-chapter selanjutnya. Volume kini terbukti menjadi rumah data yang tahan docker rm, dikelola penuh oleh Docker, dan hidup di area yang tidak perlu kita urus letaknya. Namun ada kelas kebutuhan yang justru ingin kebalikannya: kita ingin menunjuk sendiri path di host — membuka berkas konfigurasi yang kita edit, atau source code yang berubah tiap menit — dan memasangnya langsung ke container. Untuk itu Docker menyediakan bind mount, materi Chapter Bind Mount.

Referensi #