Empat mesin database kini berdiri berdampingan di mesin kita, dan masing-masing sudah menanam datanya: PostgreSQL menyimpan Kopi Torabika di volume db-data, MySQL dan MariaDB masing-masing punya katalognya, Redis menyimpan kunci-kuncinya di cache-data. Sepanjang section ini kita mengulang satu klaim seperti mantra: pasang volume, dan data akan hidup lebih lama dari container. Klaim itu sebenarnya sudah kita buktikan sekilas dengan berkas halo.txt pada Chapter Volume dan dengan siklus down/up pada Chapter Volume dan Bind Mount — tetapi untuk database, pembuktian setengah hati tidak cukup. Chapter ini mengadili klaim itu sampai tuntas: kita mengisi database, membunuh containernya secara paksa, membangkitkannya dari nol, lalu membedah volume sampai ke berkas fisiknya untuk melihat di mana sebenarnya data itu tinggal. Chapter ini juga merapikan disiplin di sekelilingnya — volume anonim yang menumpuk diam-diam, flag down -v yang menghapus segalanya, dan ritual reset bersih untuk eksperimen berikutnya.

Demo: Tabel yang Selamat dari docker rm -f #

Panggungnya tetap proyek katalog. Container db dari chapter pertama masih hidup dengan tabel produk; kita tambahkan satu tabel lagi bernama peserta berisi dua nama:

bash
$ docker exec db psql -U katalog -d katalog -c "CREATE TABLE peserta (nama TEXT);"
$ docker exec db psql -U katalog -d katalog -c "INSERT INTO peserta VALUES ('Budi'), ('Siti');"

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 2

Database katalog kini memegang dua tabel dan tiga baris. Sekarang datang terdakwanya — perintah yang paling ditakuti pemilik data:

bash
$ docker rm -f db

Output yang Dihasilkan:

text
db

Satu baris db adalah seluruh obituari container itu: writable layer, environment, nama, jaringan — semuanya lenyap. Lalu kita jalankan ulang perintah yang sama, dengan volume yang sama:

bash
$ docker run -d --name db \
    -e POSTGRES_USER=katalog \
    -e POSTGRES_PASSWORD=rahasia123 \
    -e POSTGRES_DB=katalog \
    -v db-data:/var/lib/postgresql/data \
    postgres:18-alpine

Output yang Dihasilkan:

text
c60cb7904d5887844319187b7dc024c68a9c361fbf6ce81441bfb8481a569cdb

ID container yang baru — container ini memang makhluk baru. Log startup-nya membocorkan apa yang dilihat entry point di dalam volume:

bash
$ docker logs db

Output yang Dihasilkan:

text
PostgreSQL Database directory appears to be a database; Skipping initialization
...
2026-10-13 21:44:18.402 UTC [1] LOG:  starting PostgreSQL 18.4 (Debian 18.4-1.pgdg130+1) on x86_64-pc-linux-musl, compiled by gcc (Alpine 14.2.0) 14.2.0, 64-bit
2026-10-13 21:44:18.403 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
2026-10-13 21:44:18.414 UTC [1] LOG:  database system is ready to accept connections

Skipping initialization — kalimat terpenting pada log itu. Dan kini saatnya pembuktian terakhir:

bash
$ docker exec db psql -U katalog -d katalog -c "SELECT * FROM peserta;"
$ docker exec db psql -U katalog -d katalog -c "\dt"

Output yang Dihasilkan:

text
 nama
------
 Budi
 Siti
(2 rows)

         List of relations
 Schema |  Name   | Type  |  Owner
--------+---------+-------+---------
 public | peserta | table | katalog
 public | produk  | table | katalog
(2 rows)

Mari kita telaah apa yang barusan terjadi:

  1. docker rm -f mengirim sinyal SIGKILL — container dipotong tanpa masa tenggang; seluruh writable layer beserta tulisannya dihapus dari disk host. Bila datanya menumpang di layer itu, tabel peserta seharusnya sudah mati.
  2. Volume tidak ikut terpanggil — db-data adalah objek terpisah yang hidup di luar container; docker rm tidak pernah menyentuhnya, sehingga cluster PostgreSQL di dalamnya tetap utuh menunggu penghuni baru.
  3. Skipping initialization adalah kunci — entry point mendapati direktori data berisi cluster utuh, jadi initdb dilewati; penyalaan jauh lebih cepat dan tidak satu variabel POSTGRES_* pun diproses ulang.
  4. Variabel dijalankan ulang pun tidak berbekas — kata sandi yang sama kita kirim ulang pada run kedua, dan PostgreSQL mengabaikannya; inisialisasi hanya terjadi pada volume kosong, aturan yang kita catat sejak chapter pertama section ini.
  5. Kedua tabel selamat — peserta yang barusan kita tanam sekaligus produk berisi Kopi Torabika dari chapter pertama; data lintas container, lintas waktu, tetap setia menunggu.

Mekanisme: Cluster Hidup di Volume #

Bukti di atas barusan memperlihatkan hasil; mekanismya terbaca dari metadata volume itu sendiri:

bash
$ docker volume inspect db-data

Output yang Dihasilkan:

json
[
    {
        "CreatedAt": "2026-10-13T20:41:16.812439857Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/db-data/_data",
        "Options": null,
        "Scope": "local"
    }
]

Mari kita telaah dua field yang paling bicara:

  1. CreatedAt 20:41 — volume ini lahir pada chapter pertama section ini, saat docker run pertama memasangkannya; umurnya sudah melewati satu kematian container dan akan terus berlari melewati kematian berikutnya.
  2. Mountpoint — alamat fisik di disk host tempat seluruh isi /var/lib/postgresql/data benar-benar ditulis; area /var/lib/docker adalah wilayah sang daemon, dan jalur resmi kita ke isinya adalah pemasangan volume, bukan mencomot berkasnya secara langsung.

Alur lengkap pemisahan umur itu bisa kita petakan sebagai satu diagram:

Mari kita telaah diagram di atas: sisi kiri bawah adalah nasib container — layer dan identitasnya benar-benar musnah; sisi kanan bawah adalah nasib data — beralamat tetap di mountpoint volume, tidak tersentuh putaran hidup apa pun di atasnya. Seluruh mekanisme volume bernama ini adalah materi Chapter Volume; yang baru di sini hanyalah buktinya kini berbentuk tabel SQL, bukan sekadar berkas teks.

Volume Bernama versus Volume Anonim #

Kemenangan kita bertumpu pada satu kata: nama. Volume db-data bisa dirujuk ulang oleh docker run mana pun karena namanya ditulis manusia. Kelas volume yang lain tidak seberuntung itu — dan image database kita justru produsen terbesannya:

Daftar volume di mesin cerita kita saat ini memperlihatkan sehatnya kebiasaan menamai:

bash
$ docker volume ls

Output yang Dihasilkan:

text
DRIVER    VOLUME NAME
local     cache-data
local     data-web
local     db-data
local     db-data-maria
local     db-data-mysql
local     katalog_db-data
local     latihan-volume
local     pgdata

Delapan baris itu adalah seluruh penduduk mesin cerita kita pada malam ini; di mesin pembaca, volume milik stack lain yang pernah dibuat juga akan ikut tampil di daftar yang sama. Mari kita telaah namanya satu per satu: db-data, db-data-mysql, db-data-maria, dan cache-data adalah empat penduduk section ini; katalog_db-data adalah volume Compose milik proyek katalog dengan prefix project yang sudah kita kenal; data-web, pgdata, dan latihan-volume adalah kenalan lama Section Penyimpanan Data. Tidak ada satu pun baris hex — bukti bahwa seluruh jalur yang kita lalui memasang volume secara eksplisit.

down -v di Compose adalah Factory Reset #

Di dunia docker run, bencana data datang dari lupa memasang volume. Di dunia Compose, lupa semacam itu nyaris mustahil — volume dideklarasikan di dalam compose.yaml, dan kita sudah membuktikan pada Section Docker Compose bahwa docker compose down membuang container dan network saja sambil membiarkan katalog_db-data tetap berdiri. Akan tetapi Compose menyimpan satu pintu yang jauh lebih berbahaya daripada sekadar lupa: flag -v pada perintah bongkar itu. Output berikut adalah kenyataan yang sudah kita saksikan pada Chapter Volume dan Bind Mount:

bash
$ docker compose down -v

Output yang Dihasilkan:

text
[+] Running 4/4
 ✔ Container katalog-web-1  Removed
 ✔ Container katalog-db-1   Removed
 ✔ Network katalog_default  Removed
 ✔ Volume katalog_db-data   Removed

Berkas Fisik di Balik Mountpoint #

Bukti terakhir turun satu lapisan lagi: ke berkas-berkas yang benar-benar tersimpan di volume. Kita pinjam pola helper container dari Chapter Backup dan Restore Volume — container sekali jalan yang memasang volumenya tanpa menyentuh wilayah daemon secara langsung:

bash
$ docker run --rm -v db-data:/data alpine:3.20 ls /data

Output yang Dihasilkan:

text
PG_VERSION
base
global
pg_hba.conf
pg_ident.conf
pg_logical
pg_multixact
pg_notify
pg_replslot
pg_serial
pg_snapshots
pg_stat
pg_stat_tmp
pg_tblspc
pg_twophase
pg_wal
postgresql.auto.conf
postgresql.conf

Inilah isi rumah sang data — struktur yang persis sama dengan instalasi PostgreSQL di server fisik mana pun. Mari kita telaah anggota-anggotanya yang paling penting:

  1. base — direktori tempat data setiap database sungguhan disimpan; tabel produk dan peserta hidup di dalam berkas-berkasnya.
  2. pg_wal — write-ahead log, jurnal ketahanan PostgreSQL; setiap perubahan dicatat ke sini lebih dulu sebelum diterapkan, dan inilah yang membuat database sanggup pulih dari mati mendadak — termasuk SIGKILL dari docker rm -f tadi.
  3. postgresql.conf, pg_hba.conf — konfigurasi cluster beserta aturan autentikasinya; mengubah perilaku server pada akhirnya adalah mengubah berkas-berkas ini.
  4. PG_VERSION — berkas satu baris berisi angka versi mayor cluster. Kita bacakan isinya:
bash
$ docker run --rm -v db-data:/data alpine:3.20 cat /data/PG_VERSION

Output yang Dihasilkan:

text
18

Angka 18 itu bukan sekadar hiasan: server PostgreSQL dari versi mayor yang berbeda menolak menyala di atas cluster yang tidak cocok versinya. Inilah alasan mengapa pergantian tag image seperti postgres:18-alpine ke seri mayor berbaru tidak bisa dilakukan dengan asal ganti — jalan resminya menanti Chapter Migrasi Versi Database di ujung section ini.

Ukuran Volume dan Jejak Disk #

Empat volume section ini menyimpan ratusan megabyte data, dan Docker punya pembukunya. Ringkasan pemakaian disk terbaca lewat docker system df:

bash
$ docker system df

Output yang Dihasilkan:

text
TYPE            TOTAL   ACTIVE   SIZE      RECLAIMABLE
Images          10      4        1.89GB    405MB (21%)
Containers      4       4        108B      0B (0%)
Local Volumes   8       4        379.9MB   0B (0%)
Build cache     11      0        84.2MB    84.2MB

Angka-angka ini milik mesin cerita kita dan akan berbeda di mesin pembaca. Mari kita telaah baris yang menjadi urusan kita:

  1. Local Volumes 8 — delapan volume yang kita lihat pada docker volume ls; ACTIVE 4 berarti tepat empat sedang dipasang container hidup: db-data, db-data-mysql, db-data-maria, dan cache-data.
  2. SIZE 379.9MB — penjumlahan isi seluruh volume; porsi terbesarnya datang dari direktori data MySQL yang boros sejak lahir, disusul cluster PostgreSQL, sementara cache-data si Redis hanya menyumbang ratusan kilobyte.
  3. RECLAIMABLE 0B — tidak ada volume anonim terlantar; volume bernama yang sedang tidak dipakai pun tidak dihitung sebagai sampah oleh pembersihan bawaan — selaras dengan prinsip yang sudah kita pegang: yang bernama dianggap milik.
  4. Flag -v untuk detail per volume — docker system df -v memecah baris Local Volumes menjadi tabel per volume lengkap dengan nama dan ukurannya, sehingga kita bisa mengetahui persis berapa jatah db-data tanpa menebak dari total.

Kapan Data Tidak Persisten #

Volume bukan satu-satunya tempat container menulis, dan hanya satu jenis alamat yang menjanjikan umur panjang. Tabel berikut merangkum seluruh skenario yang telah kita saksikan sepanjang course:

Skenario penulisan dataPersisten?Alasan
Volume bernama, container dihapus (docker rm -f)yavolume adalah objek terpisah dari container
Volume bernama, stack dibongkar (docker compose down)yadown hanya membuang container dan network
Writable layer tanpa mounttidaklayer hidup selama container itu saja — Chapter Writable Container Layer
tmpfs mounttidakisi hidup di memori dan lenyap saat container berhenti — Chapter tmpfs Mount
Container baru dengan volume anonim barutidakvolume lama masih ada tapi tak terjangkau tanpa nama
docker compose down -vtidakvolume sengaja dibuang bersama stack

Empat baris terakhir adalah para tersangka klasik di balik laporan “data Docker saya hilang sendiri” — dan keempatnya bukan ulah Docker yang jahat, melainkan alamat yang salah dipilih sejak awal.

Reset Bersih dan Pembersihan Arena #

Eksperimen database yang bisa diulang menuntut ritual reset yang jujur, dan bentuk lengkapnya hanya dua perintah dengan urutan yang tidak bisa ditukar — pola yang kita simpan untuk setiap kali ingin menyaksikan ulang tarian inisialisasi first-run dengan variabel yang baru:

bash
$ docker rm -f db && docker volume rm db-data

Urutannya wajib: docker volume rm menolak membuang volume yang masih dipasang container, jadi containernya harus pergi lebih dulu — pagar kecil yang menyelamatkan kita dari salah ketik sesaat sebelum bencana. Kita tidak menjalankan ritual itu sekarang — db-data justru menjadi pusat pembuktian chapter ini — tetapi pola inilah yang kita pakai untuk membereskan tiga mesin lain yang sudah menuntaskan tugas peragaannya:

bash
$ docker rm -f db-mysql db-maria cache
$ docker volume rm db-data-mysql db-data-maria cache-data

Output yang Dihasilkan:

text
db-mysql
db-maria
cache
db-data-mysql
db-data-maria
cache-data

Sementara itu db dan db-data sengaja kita biarkan hidup — tabel produk dan peserta di dalamnya menjadi titik berangkat chapter berikutnya. Dan justru di titik inilah satu disiplin terakhir perlu dipasang: volume melindungi data dari container, tetapi tidak dari apa pun yang terjadi pada satu disk itu sendiri. docker volume rm yang barusan kita jalankan memusnahkan data MySQL dan MariaDB tanpa sidang pengadilan — sebelum menjalankan hal serupa pada volume yang berisi data berharga, satu-satunya jalan aman adalah mencadangkannya lebih dulu, dan ritual pg_dump serta mysqldump untuk itu adalah materi Chapter Backup Database di seberang sana.

Sejauh ini inisialisasi kita selalu manual: container menyala, kita menyapa lewat klien, lalu mengetik CREATE TABLE dengan tangan sendiri. Rutinitas itu tidak akan bertahan di dunia nyata — rekan tim yang menjalankan compose.yaml di mesin baru tidak mau (dan tidak seharusnya) menyalin-tempel skema SQL dari obrolan. Docker menyediakan pintu resmi untuk hal ini: direktori docker-entrypoint-initdb.d yang isinya dijalankan otomatis pada inisialisasi pertama, ditambah pola service one-shot untuk mengisi data pada stack Compose. Chapter Inisialisasi Database menutup tarian inisialisasi yang selama ini kita kerjakan dengan tangan.

Referensi #