September 27, 2026

Docker Volume.

Memahami named volume, bind mount, dan tmpfs di Docker: kapan memakai masing-masing, cara backup & restore volume, serta jebakan permission/UID.

Container itu sementara: begitu container dihapus, semua file yang ditulis di dalam layer-nya ikut hilang. Supaya data seperti database, upload user, atau cache tetap ada, Docker menyediakan tiga jenis mount: named volume, bind mount, dan tmpfs. Catatan ini merangkum bedanya, cara backup/restore, dan masalah permission yang paling sering muncul.


1. Tiga Jenis Mount

JenisData disimpan diDikelola olehCocok untuk
Named volumeArea Docker (/var/lib/docker/volumes/... di Linux, di dalam VM di macOS/Windows)DockerDatabase, data persisten, vendor//node_modules/ saat dev
Bind mountPath apa pun di host (./src, /etc/nginx/...)KamuSource code saat development, file config
tmpfsRAM (hilang saat container stop)KernelData sementara/sensitif, cache cepat

Aturan praktis: data milik aplikasi β†’ named volume, file milik developer β†’ bind mount, data yang tidak boleh menyentuh disk β†’ tmpfs.


2. Named Volume

docker volume create pgdata
docker volume ls
docker volume inspect pgdata
docker run -d --name db \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:17

Sintaks --mount lebih eksplisit (disarankan di dokumentasi resmi karena lebih jelas dibaca):

docker run -d --name db \
  --mount type=volume,source=pgdata,target=/var/lib/postgresql/data \
  postgres:17

Perilaku penting:

  • Jika volume kosong dan path target di image sudah berisi file, Docker menyalin isi image ke volume saat pertama kali dipakai. Bind mount tidak melakukan ini β€” ia langsung menutupi isi folder.
  • Volume tetap ada walau container dihapus (docker rm). Ia hanya hilang lewat docker volume rm, docker volume prune, atau docker compose down -v.

Di Compose :

services:
  mysql:
    image: mysql:8.4
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  mysql-data:            # dibuat otomatis dengan nama <project>_mysql-data
  shared-uploads:
    external: true       # volume sudah dibuat di luar Compose, jangan dibuat ulang

3. Bind Mount

docker run --rm -it \
  -v "$(pwd)":/app \
  -w /app \
  node:22-alpine npm test

Versi --mount (akan error kalau path host tidak ada β€” lebih aman daripada -v yang diam-diam membuat folder kosong):

docker run --rm --mount type=bind,source="$(pwd)",target=/app,readonly node:22-alpine ls /app

Tambahkan :ro / readonly untuk file config yang tidak boleh diubah container:

volumes:
  - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro

4. tmpfs

docker run -d --tmpfs /app/cache:rw,size=64m myapp
services:
  app:
    tmpfs:
      - /tmp

tmpfs hanya tersedia untuk container Linux. Isinya hilang begitu container berhenti, jadi jangan simpan apa pun yang perlu dipertahankan.


5. Backup & Restore Volume

Cara paling portabel: jalankan container sementara yang me-mount volume, lalu tar isinya ke folder host.

Backup

docker run --rm \
  -v mysql-data:/data:ro \
  -v "$(pwd)":/backup \
  alpine tar czf /backup/mysql-data-$(date +%F).tar.gz -C /data .

Restore ke volume baru

docker volume create mysql-data-restore
docker run --rm \
  -v mysql-data-restore:/data \
  -v "$(pwd)":/backup \
  alpine sh -c "tar xzf /backup/mysql-data-2026-09-27.tar.gz -C /data"

Khusus database: pakai dump, bukan salin file

Menyalin file mentah database yang sedang berjalan bisa menghasilkan backup korup. Hentikan dulu containernya (docker compose stop mysql) sebelum tar, atau lebih baik gunakan tool dump bawaan:

# MySQL
docker compose exec mysql sh -c 'mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --single-transaction app' > app.sql
docker compose exec -T mysql sh -c 'mysql -u root -p"$MYSQL_ROOT_PASSWORD" app' < app.sql

# PostgreSQL
docker compose exec -T db pg_dump -U postgres app > app.sql
docker compose exec -T db psql -U postgres app < app.sql

Flag -T mematikan alokasi TTY sehingga redirect < / > berjalan benar.


6. Permission & UID: Sumber Masalah Nomor Satu

Linux tidak peduli nama user, hanya UID/GID angka. Proses di container sering berjalan sebagai root (UID 0) atau user khusus seperti www-data (UID 33) / node (UID 1000). Saat menulis ke bind mount, file di host ikut memiliki UID tersebut.

Gejala umum:

  • Setelah composer install di container, folder vendor/ di host milik root β†’ tidak bisa dihapus tanpa sudo.
  • Laravel error The stream or file "storage/logs/laravel.log" could not be opened: Permission denied.
  • Postgres/MySQL gagal start karena folder data di bind mount tidak bisa ditulis.

Solusi dari yang paling sederhana:

1. Jalankan container sebagai UID kamu

docker run --rm -u "$(id -u):$(id -g)" -v "$(pwd)":/app -w /app composer install
services:
  app:
    user: "${UID:-1000}:${GID:-1000}"

2. Samakan UID user di image dengan host (lewat build arg)

FROM php:8.4-fpm
ARG UID=1000
ARG GID=1000
RUN usermod -u ${UID} www-data && groupmod -g ${GID} www-data
USER www-data
docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g)

3. Untuk data service (database) pakai named volume, bukan bind mount. Docker mengatur kepemilikannya dan masalah UID praktis hilang.

4. chown di Dockerfile untuk folder yang ditulis aplikasi:

RUN chown -R www-data:www-data storage bootstrap/cache

Catatan macOS: Docker Desktop memetakan kepemilikan file bind mount secara otomatis, jadi masalah UID jauh lebih jarang dibanding di Linux. Jangan kaget kalau setup yang β€œjalan di Mac” error di server Linux.


7. Membersihkan Volume

docker volume ls -f dangling=true   # volume yang tidak dipakai container mana pun
docker volume prune                 # hapus volume anonim yang tidak dipakai
docker volume prune -a              # hapus SEMUA volume tidak terpakai (termasuk named)
docker system df -v                 # lihat ukuran per volume

8. Pitfalls

  • docker compose down -v menghapus named volume project β†’ data DB hilang. Backup dulu.
  • Bind mount menutupi isi image. Kalau image sudah punya vendor/ lalu kamu bind mount ./:/var/www/html, vendor/ dari image β€œhilang”. Solusinya named volume khusus untuk vendor/.
  • Typo path dengan -v β†’ Docker membuat folder kosong di host. Pakai --mount agar error.
  • Nama volume Compose diberi prefix nama project (myapp_mysql-data). Saat backup dengan docker run, gunakan nama lengkapnya (cek docker volume ls).
  • Volume anonim (-v /data tanpa nama) sulit dilacak dan menumpuk. Selalu beri nama.
  • Upgrade versi mayor database (MySQL 8.0 β†’ 8.4, Postgres 16 β†’ 17) tidak selalu kompatibel dengan data volume lama. Dump β†’ restore lebih aman.

Referensi: Volumes , Bind mounts , tmpfs mounts .

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.

Comments