Chapter Port dan Network ditutup dengan satu pengamatan yang menusuk: begitu docker compose down dijalankan dan stack dibangkitkan kembali, seluruh isi database katalog lenyap — service db menyala sebagai cluster PostgreSQL baru yang bersih, seolah tabel yang tadinya terisi tidak pernah ada. Kehilangan semacam itu mengganggu bahkan untuk latihan, apalagi bila yang hilang adalah data pelanggan yang terkumpul berbulan-bulan. Penyebabnya bukan ulah Compose yang jahat: perintah down memang hanya membuang container dan network, sementara selama ini tulisan PostgreSQL mendarat di penyimpanan yang mati bersama container. Chapter ini memindahkan urusan umur panjang data ke dalam compose.yaml — lewat kunci volumes untuk volume bernama, bind mount untuk source code, dan tmpfs untuk data sekali jalan.
Mengapa Data Musnah Saat down #
Setiap container menyimpan tulisannya pada writable layer — lapisan tipis di atas image yang bersifat read-only dan hidupnya persis selama container itu sendiri, mekanisme yang kita bedah tuntas pada Chapter Writable Container Layer. Perintah docker compose down membuang container web dan db beserta network katalog_default, dan tidak satu pun objek itu dijemput kembali: docker compose up berikutnya menciptakan semuanya dari nol. Container db yang baru berdiri di atas layer kosong, sehingga entry point PostgreSQL tidak menemukan cluster apa pun di /var/lib/postgresql/data dan memutuskan menjalankan initdb dari awal. Variabel POSTGRES_USER dan POSTGRES_DB tetap membangun user serta database katalog seperti biasa, tetapi isi tabel yang lama tidak pernah diundang datang kembali.
Obatnya adalah melepaskan umur data dari umur container — pola yang sudah kita praktikkan manual lewat docker volume create dan flag -v pada Chapter Volume. Compose memindahkan pola itu ke dunia deklaratif: cukup dua suntingan kecil pada compose.yaml, satu di dalam service db dan satu di level teratas berkas. Hasilnya berbunyi: “setiap container db pasti memasang volume bernama db-data di direktori datanya”, dan pernyataan itu berlaku untuk semua up selama-lamanya.
Volume Bernama untuk Service db #
Berikut compose.yaml katalog setelah volume bernama dipasang. Perhatikan bahwa tidak ada service baru yang ditambahkan — volume bukan service, melainkan objek penyimpanan yang dipasang ke service yang sudah ada.
services:
web:
build: .
ports:
- "8080:3000"
db:
image: postgres:18-alpine
environment:
POSTGRES_USER: katalog
POSTGRES_PASSWORD: rahasia123
POSTGRES_DB: katalog
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:Mari kita telaah tiga kunci yang bekerja sama pada berkas di atas:
volumesdi dalam servicedb— daftar mount yang dipasang ke setiap container hasil service ini; satu barisdb-data:/var/lib/postgresql/databerarti: pasang volume bernamadb-datake path/var/lib/postgresql/datadi dalam container, formatSUMBER:TARGETyang sama dengan flag-vpadadocker run.- Path target
/var/lib/postgresql/data— direktori data resmi image postgres; entry point image menjalankaninitdbserta membaca cluster dari path inilah, jadi memasang volume di titik yang tepat membuat seluruh isi database — tabel, indeks, sampai konfigurasi cluster — tinggal di dalam volume, bukan di penyimpanan container. volumes:level teratas — mendeklarasikandb-datasebagai volume milik project katalog; Compose membuat objek tersebut sekali saatuppertama dan memakai ulang objek yang sama padaupberikutnya. Blok teratas juga kelak menjadi tempat menyetel opsi lanjutan sepertidriveratauexternaltanpa menyentuh service.
Budi mematikan dulu stack warisan chapter sebelumnya dengan docker compose down, menyimpan suntingan di atas, lalu menyalakan kembali stack-nya:
$ docker compose up -dOutput yang Dihasilkan:
[+] Running 3/3
✔ Network katalog_default Created
✔ Container katalog-db-1 Started
✔ Container katalog-web-1 StartedNama Volume dengan Prefix Project #
Volume yang barusan diciptakan Compose tidak bernama db-data persis. Daftar volume di mesin membuktikannya:
$ docker volume lsOutput yang Dihasilkan:
DRIVER VOLUME NAME
local 3f5c8a1e7b2d4f6a9c0e2b5d8f1a3c6e9b2d5f8a1c4e7b0d3f6a2c9e5b1d
local 7b2e9f4c1a8d3e6f0c5a2d7b9e1f4a6c8b3d0e7f2a5c9b1d4f8a6e3c0b2d
local data-web
local katalog_db-data
local latihan-volume
local pgdata
...Mari kita telaah baris-baris di atas:
katalog_db-data, bukandb-data— Compose memberi prefix nama project pada semua objek yang diciptakannya, dengan pola<project>_<nama>; nama project diambil dari nama direktori (katalog), persis pola yang sudah kita saksikan pada networkkatalog_defaultdan containerkatalog-db-1di Chapter Port dan Network.- Prefix mencegah tabrakan antar project — dua project berbeda di mesin yang sama boleh sama-sama punya volume bernama
db-data; keduanya akan hidup berdampingan sebagaikatalog_db-datadankasir_db-datatanpa saling menginjak. data-web,pgdata, danlatihan-volume— kenalan lama dari Section Penyimpanan Data yang memang sengaja dibiarkan hidup; volume hanya mati bila dihapus eksplisit, dandocker volume lsjujur melaporkan semua penghuninya.- Dua baris heksadesimal 64 karakter — volume anonim yang keberadaannya sengaja kita lewatkan sejenak; kisah di baliknya dibuka tuntas pada bagian akhir chapter ini.
Demo: Data yang Selamat dari down #
Inilah demonstrasi inti chapter ini sekaligus balasan langsung atas kehilangan data di penutup chapter sebelumnya. Kita isi database dengan data nyata, bongkar stack sampai ke akarnya, bangkitkan kembali, lalu bertanya kepada PostgreSQL: apakah tabel produk masih mengingat isinya?
$ docker compose exec db psql -U katalog -d katalog -c "CREATE TABLE produk (nama TEXT, harga INTEGER);"
$ docker compose exec db psql -U katalog -d katalog -c "INSERT INTO produk VALUES ('Kopi Arabika Gayo', 85000), ('Teh Melati', 45000);"Output yang Dihasilkan:
CREATE TABLE
INSERT 0 2Dua baris produk kini tercatat di dalam volume katalog_db-data. Saatnya menjalankan perintah yang pada chapter sebelumnya memusnahkan segalanya:
$ docker compose downOutput yang Dihasilkan:
[+] Running 3/3
✔ Container katalog-web-1 Removed
✔ Container katalog-db-1 Removed
✔ Network katalog_default Removed$ docker compose up -dOutput yang Dihasilkan:
[+] Running 3/3
✔ Network katalog_default Created
✔ Container katalog-db-1 Started
✔ Container katalog-web-1 Started$ docker compose exec db psql -U katalog -d katalog -c "SELECT * FROM produk;"Output yang Dihasilkan:
nama | harga
-------------------+-------
Kopi Arabika Gayo | 85000
Teh Melati | 45000
(2 rows)Dua baris yang sama masih berdiri setelah siklus down dan up lengkap. Mari kita telaah mengapa kali ini datanya selamat:
- Output
downtidak memuat barisVolume ... Removed— perintahdownmembuang container dan network saja; volume bernama sengaja tidak tersentuh karena volume adalah objek terpisah yang umurnya melebihi stack. - Container
dbbaru memasang volume yang sama — deklarasidb-data:/var/lib/postgresql/datamembuat Compose mencari volumekatalog_db-datayang sudah ada, bukan menciptakan yang baru; perintahupkedua dengan demikian tidak pernah mulai dari layer kosong. initdbdilewati — entry point PostgreSQL mendapati direktori data berisi cluster utuh, jadi inisialisasi tidak diulang dan servicedbsiap lebih cepat; perilaku yang sama sudah kita saksikan pada Chapter Volume.SELECTmengembalikan data lama — tabelprodukbeserta isinya tidak pernah pindah rumah; sejak perintahINSERTpertama, alamatnya adalah mountpoint volume di disk host, bukan writable layer.- Siklus boleh diulang sebanyak apa pun —
downdanupbisa bergantian puluhan kali; selama nama volume dalam compose.yaml tidak berubah, data tetap di tempatnya.
Sintaks Pendek dan Sintaks Panjang #
Kunci volumes di dalam service menerima dua tata tulis, mengikuti pembagian yang sudah akrab antara flag -v dan --mount di dunia CLI. Sintaks pendek menuliskan mount sebagai satu string SUMBER:TARGET, sedangkan sintaks panjang memecahnya menjadi pasangan kunci yang menyebut jenis mount secara gamblang:
volumes:
- type: volume
source: db-data
target: /var/lib/postgresql/data| Aspek | Sintaks pendek | Sintaks panjang |
|---|---|---|
| Bentuk | db-data:/var/lib/postgresql/data | Kunci type, source, target baris demi baris |
| Jenis mount | Ditebak dari bentuk sumber — tanpa / berarti volume | Dinyatakan gamblang lewat type: volume |
| Mode akses | Ditambah di ujung: db-data:/path:ro | Kunci read_only: true |
| Opsi tmpfs | Tidak tersedia | Sub-blok tmpfs dengan size |
| Paling tepat untuk | Mount sederhana yang terbaca sekilas | Deklarasi yang butuh opsi tambahan |
Kedua tata tulis menunjuk mount yang sama dan saling menggantikan; pilihannya urusan kejelasan, bukan fungsi. Bentuk pendek cukup untuk kebanyakan service data, sementara bentuk panjang membayar dirinya begitu opsi seperti read_only atau batasan ukuran tmpfs ikut bermain.
Bind Mount untuk Source Code #
Volume menempatkan data di wilayah yang dikelola Docker, tetapi saat mengembangkan aplikasi kita justru ingin sebaliknya: source code tinggal di host, dikerjakan dengan editor biasa, dan container cukup membacanya. Itu pekerjaan bind mount, dan di Compose mount jenis ini ditandai sumber yang berawalan / atau ./:
web:
build: .
ports:
- "8080:3000"
volumes:
- type: bind
source: ./src
target: /appBentuk pendeknya lebih ringkas: ./src:/app. Path ./src dihitung relatif terhadap lokasi compose.yaml, dan isi path host menutup path /app di dalam container apa adanya — tanpa pra-isi, dua arah, persis mekanisme Chapter Bind Mount.
Pola lain yang terbawa dari chapter CLI adalah bind berkas konfigurasi tunggal. Menambahkan read_only: true menjaga container agar tidak pernah menulis balik ke berkas milik host:
- type: bind
source: ./nginx.conf
target: /etc/nginx/nginx.conf
read_only: trueMount read-only seperti ini akan kita bahas lebih dalam lagi pada Chapter Mount Read-Only di Section Penyimpanan Data; untuk saat ini cukup dicatat bahwa kuncinya sama, hanya pindah rumah ke compose.yaml.
tmpfs di dalam compose #
Kelas data terakhir justru ingin tidak pernah menyentuh disk sama sekali: cache yang boleh hilang kapan pun atau berkas sementara yang tidak boleh meninggalkan jejak. Compose menyediakan kunci tmpfs untuk kebutuhan itu:
web:
build: .
ports:
- "8080:3000"
tmpfs:
- /app/cacheBentuk panjangnya kembali lewat kunci volumes dan membuka opsi batasan ukuran:
volumes:
- type: tmpfs
target: /app/cache
tmpfs:
size: 16000000Mari kita telaah dua blok di atas:
tmpfs: /app/cache— memasang filesystem memori pada path tersebut di dalam container; isi hilang begitu container berhenti, persis perilaku Chapter tmpfs Mount.size: 16000000— membatasi tmpfs pada 16 juta byte (sekitar 16 MB); angka ditulis dalam satuan byte, dan tanpa batasan eksplisit ukurannya diserahkan kepada kernel host.
Nasib Volume Saat down #
Kita sudah membuktikan bahwa docker compose down membiarkan volume bernama tetap berdiri. Perilaku ini disengaja: pembersihan stack harus aman untuk data, dan Compose memilih untuk tidak pernah menghapus sesuatu yang tidak bisa dikembalikan. Konsekuensinya, satu-satunya cara membuat down ikut membuang volume adalah menuliskan flag -v (bentuk panjangnya --volumes) secara eksplisit:
$ docker compose down -vOutput yang Dihasilkan:
[+] Running 4/4
✔ Container katalog-web-1 Removed
✔ Container katalog-db-1 Removed
✔ Network katalog_default Removed
✔ Volume katalog_db-data RemovedBaris Volume katalog_db-data Removed itulah bedanya — dan baris tersebut baru muncul karena kita memintanya.
Volume Anonim dari Image #
Sekarang saatnya menepati janji tentang dua baris heksadesimal pada docker volume ls. Image postgres ternyata membawa instruksi VOLUME /var/lib/postgresql/data di Dockerfile-nya — instruksi yang kita kenal pada Chapter VOLUME dan LABEL sebagai penanda titik data. Efeknya: setiap kali container dari image itu dijalankan tanpa mount eksplisit pada path tersebut, daemon diam-diam melahirkan volume anonim dan memasangkannya.
Dua siklus up pada chapter-chapter sebelumnya dengan demikian meninggalkan dua volume anonim terlantar: container db pertama mengisi volume anonimnya dengan cluster hasil initdb, container db kedua mendapat volume anonim baru yang segar. Data yang kita kira musnah sebenarnya masih ada — tergeletak di volume anonim pertama yang tidak lagi dirujuk container mana pun. Inilah mengapa kehilangan data pada penutup Chapter Port dan Network terasa seperti ulah writable layer, padahal biang keroknya adalah volume anonim yang berganti-ganti tanpa diminta.
Membereskan dua baris sisa itu cukup dengan docker volume prune, karena sasaran bawaannya memang volume anonim yang tidak dipakai container mana pun:
$ docker volume pruneOutput yang Dihasilkan:
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:
3f5c8a1e7b2d4f6a9c0e2b5d8f1a3c6e9b2d5f8a1c4e7b0d3f6a2c9e5b1d
7b2e9f4c1a8d3e6f0c5a2d7b9e1f4a6c8b3d0e7f2a5c9b1d4f8a6e3c0b2d
Total reclaimed space: 75.6MBMari kita telaah hasil pembersihan ini:
- Dua volume anonim terbuang — keduanya bekas container
dbdari siklus up sebelum kita memasangdb-data; ruang disk 75.6 MB kembali ke host. katalog_db-dataselamat — volume bernama berada di luar jangkauanprunebawaan; prinsipnya tetap sama dengan Chapter Volume: yang anonim dianggap sampah, yang bernama dianggap milik.- Perintah konfirmasi
[y/N]— pembersihan tidak bisa dibatalkan, jadi bacalahdocker volume lssekali lagi sebelum menekany.
Volume, Bind Mount, dan tmpfs dalam compose #
Ketiga jenis mount kini lengkap di tangan kita dalam bentuk deklaratif. Tabel berikut merangkum posisi masing-masing di dalam compose.yaml:
| Aspek | Volume bernama | Bind mount | tmpfs |
|---|---|---|---|
| Bentuk khas | db-data:/var/lib/postgresql/data | ./src:/app atau type: bind | tmpfs: /app/cache atau type: tmpfs |
| Lokasi fisik | /var/lib/docker/volumes/... | Path host yang kita tunjuk | Memori host |
Selamat dari down | Ya — volume tetap berdiri | Ya — berkas tetap milik host | Tidak — hilang saat container berhenti |
| Pra-isi dari image | Ya, bila volume masih kosong | Tidak — isi host menutup path | Tidak |
| Kegunaan khas | Database dan data aplikasi | Source code development, konfigurasi | Cache, data sekali jalan |
Data katalog kini berumur panjang: down boleh datang berkali-kali, tabel produk tetap setia di katalog_db-data. Namun stack kita masih menyimpan satu tugas yang belum terselesaikan — service web sampai detik ini belum tahu cara mencapai db: di alamat berapa, dengan kredensial apa, dan ke database yang bernama apa. Jawaban untuk kelas kebutuhan itu bukan mount, melainkan environment variable yang disuntikkan Compose ke setiap service — materi Chapter Environment dan env File.
Referensi #
- Volumes in Compose
— pola volume bernama, prefix project, dan perilakunya terhadap
down. - Compose File Reference — Services
— sintaks pendek dan panjang kunci
volumessertatmpfs. - docker compose down — CLI Reference
— perilaku
downterhadap volume dan flag-v. - Volumes — konsep volume, bind mount, dan tmpfs di balik kunci compose.