Rumah dan identitas kini tertata rapi: WORKDIR memberi myapp alamat /app, dan USER menurunkan wewenang proses ke user app nomor 1000. Dua instruksi yang menunggu di chapter ini sering dianggap kembar karena keduanya “kelihatannya cuma metadata” di mata pemula — padahal nasibnya bertolak belakang. VOLUME diam-diam mengubah cara container menyimpan data setiap kali image dijalankan, sementara LABEL memang benar-benar tidak berbuat apa pun selain menyimpan catatan. Budi akan menemukan bahwa instruksi pertama bisa melahirkan tumpukan volume anonim tanpa ia sadari, sedangkan instruksi kedua justru menjadi standar modern untuk memberi image identitas yang terbaca mesin.
VOLUME Menandai Titik Data #
VOLUME /data menandai sebuah path di dalam image sebagai titik data: lokasi yang isinya layak dipisahkan dari umur container. Efeknya baru muncul saat container dijalankan: bila operator tidak memasang apa pun pada path itu, daemon otomatis membuat sebuah volume anonim dan memasangkannya ke /data sebelum container menyala. Sebaliknya, tanpa deklarasi VOLUME dan tanpa mount apa pun, tulisan ke /data akan mengendap di writable layer container — lapisan sementara yang mati bersama container, persis mekanisme yang kita bedah pada Chapter Writable Container Layer. Deklarasi VOLUME dengan demikian berfungsi sebagai jaminan: data pada path yang ditandai selalu berakhir di volume, bukan di layer yang terbuang.
Volume Anonim yang Lahir Sendiri #
Untuk menyaksikannya dengan mata kepala sendiri, Budi membangun image mainan kecil bernama pencatat: Alpine yang menulis satu berkas ke /data lalu langsung berhenti:
FROM alpine:3.20
VOLUME /data
CMD ["sh", "-c", "echo catatan dari container > /data/catatan.txt"]$ docker build -t pencatat:1.0 .Output yang Dihasilkan:
[+] Building 1.8s (8/8) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 98B 0.0s
=> [internal] load metadata for docker.io/library/alpine:3.20 0.6s
=> [1/2] FROM docker.io/library/alpine:3.20 0.0s
=> => resolve docker.io/library/alpine:3.20 0.6s
=> [2/2] VOLUME /data 0.0s
=> exporting to image 0.2s
=> => exporting layers 0.1s
=> => writing image sha256:0d4b8e2a6c1f3d7e9a5b2c8d4f6a1e3c7b5d9f2a4c6e8b0d2f4a6c8e1b3d5f7a 0.1s
=> => naming to docker.io/library/pencatat:1.0 0.0s$ docker run -d --name pencatat-satu pencatat:1.0Output yang Dihasilkan:
5f0a3c8e1b7d4f2a6c9e0b3d5f8a2c4e7b1d9f3a6c5e0b7d2f4a8c1e3b6d9f0aContainer menulis catatannya ke /data lalu berhenti beberapa ratus milidetik kemudian. Daftar volume di mesin Budi kini berisi satu penghuni baru:
$ docker volume lsOutput yang Dihasilkan:
DRIVER VOLUME NAME
local 5d3f8a1c9b2e7d4f6a0c8e2b4d6f9a1c3e5b7d9f2a4c6e8b0d2f4a6c8e1b3df2Mari kita telaah baris tunggal di atas:
- Satu volume anonim lahir sendiri — daemon menciptakannya saat container start karena
/datadideklarasikan VOLUME dan tidak ada mount yang dipasang operator. - Nama berupa 64 karakter hex — volume anonim tidak punya nama yang bisa diingat manusia; dalam jumlah besar, daftar seperti ini berubah menjadi derau hex yang menakutkan.
- Container sudah berhenti, volume tidak — pencatat-satu berstatus Exited, tetapi volumenya tetap berdiri di penyimpanan lokal menunggu dipakai atau dibuang.
Apakah catatannya benar-benar tersimpan di volume itu? Budi memasang volume tersebut ke container sekali pakai dan membaca isinya:
$ docker run --rm -v 5d3f8a1c9b2e7d4f6a0c8e2b4d6f9a1c3e5b7d9f2a4c6e8b0d2f4a6c8e1b3df2:/data alpine:3.20 cat /data/catatan.txtOutput yang Dihasilkan:
catatan dari containerBerkas itu memang tinggal di volume, bukan di writable layer — satu-satunya alasan berkas tersebut selamat dari kematian container. Pola -v nama-volume:/path di perintah di atas adalah cara memasang volume secara eksplisit, dan kita dalami nanti pada Chapter Volume serta Chapter Konsep Persistence.
Setiap Run Mendapat Volume Baru #
Kejutan sebenarnya muncul pada percobaan kedua. Budi menjalankan image yang sama sekali lagi:
$ docker run -d --name pencatat-dua pencatat:1.0Output yang Dihasilkan:
2c7e1a5f9b3d4e8a6c0f2b5d7a1e4c8f3b6d9a2e5c7f1b4d8a0e3c6f2b9d5a1e$ docker volume lsOutput yang Dihasilkan:
DRIVER VOLUME NAME
local 5d3f8a1c9b2e7d4f6a0c8e2b4d6f9a1c3e5b7d9f2a4c6e8b0d2f4a6c8e1b3df2
local 8a1e4c7b2d9f3a6c5b0d7f2e4c9a1b3d5f7c9e1a3b5d7f9c1e3a5b7d9f1c3e5fMari kita telaah hasil kedua ini:
- Volume kedua lahir, bukan volume lama dipakai ulang — pencatat-dua mendapat volume anonim segar; isi catatan milik pencatat-satu tidak ikut pindah ke sana.
- VOLUME tidak memberi persistensi antar container — instruksi ini hanya memindahkan tulisan dari writable layer ke volume milik masing-masing container; data yang benar-benar dibagikan antar container datang dari volume bernama yang sengaja dipasang ulang, materi Chapter Volume dan Konsep Persistence.
- Pembersihan latihan — Budi menghapus kedua container dengan
docker rm, lalu membuang kedua volume hex dengandocker volume rm; flag--rmpadadocker rundan flag-vpadadocker rmsebenarnya sudah ikut membuang volume anonim container terkait, jadi tumpukan hanya menunggu container yang dibuat tanpa keduanya.
LABEL Metadata Key-Value #
LABEL menempelkan pasangan key-value pada konfigurasi image — dan di sanalah seluruh kemiripannya dengan VOLUME berakhir, karena LABEL tidak punya efek runtime apa pun. Bentuk dasarnya LABEL kunci="nilai"; satu instruksi bisa memuat banyak pasangan, nilai boleh memuat spasi selama berada di dalam tanda kutip, dan label menempel pada image hingga dibaca lewat docker image inspect. Label juga diwariskan: image yang dibangun di atas image ber-label akan mewarisi seluruh label induknya, dan kunci yang ditulis ulang oleh anak akan menimpa nilai induk.
LABEL Standar OCI #
Kunci LABEL bebas kita karang, tetapi komunitas container sudah sepakat memakai satu kumpulan kunci baku lewat Open Container Initiative (OCI): kunci berawalan org.opencontainers.image.*. Kunci baku ini membuat tool apa pun — registry, katalog internal, pemindai keamanan — membaca identitas image dengan cara yang sama, karena nama-nama kuncinya tidak lagi menyisakan ruang tebak. Budi memperbarui Dockerfile myapp memakai blok label standar itu, menggantikan label versi sederhana milik versi sebelumnya:
# syntax=docker/dockerfile:1
# Basis: Node.js 22 di atas Alpine
FROM node:22-alpine
# Metadata standar OCI
LABEL org.opencontainers.image.title="myapp" \
org.opencontainers.image.description="API kecil milik Budi" \
org.opencontainers.image.version="1.5" \
org.opencontainers.image.authors="Budi <[email protected]>" \
org.opencontainers.image.source="git.example.com/budi/myapp" \
org.opencontainers.image.licenses="MIT"
# Direktori kerja untuk semua instruksi berikutnya
WORKDIR /app
# Buat user dan grup non-root selagi build masih berjalan sebagai root
RUN addgroup -g 1000 app && adduser -u 1000 -G app -H -D app
# Berkas aplikasi mendarat di /app
COPY package.json server.js ./
# Mulai baris ini semua instruksi berjalan sebagai user app
USER app
# Port yang didengarkan aplikasi
EXPOSE 8000
# Perintah bawaan container
CMD ["node", "server.js"]$ docker build -t myapp:1.5 .Output yang Dihasilkan:
[+] Building 4.4s (14/14) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 889B 0.0s
=> [internal] load metadata for docker.io/library/node:22-alpine 0.7s
=> [1/7] FROM docker.io/library/node:22-alpine 0.0s
=> => resolve docker.io/library/node:22-alpine 0.7s
=> [2/7] LABEL org.opencontainers.image.title=myapp ... 0.0s
=> [3/7] WORKDIR /app 0.0s
=> [4/7] RUN addgroup -g 1000 app && adduser -u 1000 -G app -H -D app 0.5s
=> [5/7] COPY package.json server.js ./ 0.1s
=> => transferring context: 1546B 0.0s
=> [6/7] USER app 0.0s
=> [7/7] EXPOSE 8000 0.0s
=> exporting to image 0.2s
=> => exporting layers 0.1s
=> => writing image sha256:4e8d1a6c92f73b5e7d1a4c9f2b6d8e0a5f3c7b1e4d8a2f5c9e0b3d6f1a7c5e9d 0.1s
=> => naming to docker.io/library/myapp:1.5 0.0sOutput di atas terpotong pada baris [2/7] — BuildKit mencetak seluruh pasangan key-value LABEL dalam satu baris panjang yang melebihi lebar layar. Sekarang kita baca label yang tertanam pada image hasilnya:
$ docker image inspect myapp:1.5 --format '{{json .Config.Labels}}'Output yang Dihasilkan:
{
"org.opencontainers.image.authors": "Budi <[email protected]>",
"org.opencontainers.image.description": "API kecil milik Budi",
"org.opencontainers.image.licenses": "MIT",
"org.opencontainers.image.source": "git.example.com/budi/myapp",
"org.opencontainers.image.title": "myapp",
"org.opencontainers.image.version": "1.5"
}Mari kita telaah hasil bacaan di atas:
{{json .Config.Labels}}— template Go dengan kata kuncijson, gaya yang sama dengan{{json .Config.Env}}pada Chapter Image Inspect dan History; hasilnya objek JSON valid yang siap diteruskan ke parser lain.- Kunci tampil alfabetis — inspect mencetak pasangan secara terurut meskipun Dockerfile menuliskannya dengan urutan lain; urutan penulisan di Dockerfile tidak bermakna bagi hasil akhir.
- Nama domain terbalik sebagai awalan —
org.opencontainers.image.*mengikuti konvensi penamaan domain terbalik supaya kunci baku tidak bertabrakan dengan kunci milik tool lain; kunci bebas sepertiversiyang kita pakai sebelumnya tetap sah, tetapi tidak punya makna baku di mata mesin. - Nilai
sourcepada contoh adalah alamat fiktif — myapp memang proyek latihan; pada proyek nyata, nilaisourceberisi URL lengkap repositori kode sumber image, dan nilai inilah yang ditelusuri kembali saat audit.
Label bukan hiasan. Halaman repository di registry menampilkan title dan description; tool pengaudit membaca version dan source untuk melacak dari mana sebuah image berasal dan dibangun dari kode versi berapa — kerangka pelacakan asal-usul image yang kita bahas pada Chapter Image Provenance di Section Keamanan Docker. Untuk kebutuhan chapter ini, satu kenyataan sudah cukup meyakinkan: label terbaca tanpa menjalankan container apa pun, karena LABEL bekerja sepenuhnya di dunia metadata.
VOLUME versus LABEL #
Tabel berikut merangkum dua instruksi yang kita bahas sekaligus mempertegas perbedaan nasibnya:
| Aspek | VOLUME | LABEL |
|---|---|---|
| Jenis instruksi | Deklarasi penyimpanan — menandai titik data | Metadata — pasangan key-value |
| Efek saat container berjalan | Volume anonim dibuat otomatis bila path tidak dimount | Tidak ada efek — hanya dibaca saat dibutuhkan |
| Di mana terlihat | docker volume ls dan field Mounts pada inspect | field Config.Labels pada docker image inspect |
| Jejak yang menumpuk | Volume anonim hex bila jarang dibersihkan | Tidak ada — label hanya catatan |
| Kapan paling tepat dipakai | Image yang jelas-jelas menyimpan data layak-pisah | Setiap image: identitas, versi, lisensi, penjaga |
VOLUME dan LABEL melengkapi peta instruksi hingga nyaris penuh — tinggal satu instruksi runtime yang belum kita sentuh: HEALTHCHECK. Selama ini kolom STATUS hanya tahu dua kabar: Up atau Exited; padahal aplikasi di dalam container bisa sakit menggantung jauh sebelum proses utamanya mati. Chapter HEALTHCHECK mengajari image membawa cara memeriksa dirinya sendiri, sehingga status container berhenti melaporkan kabar baik yang palsu.
Referensi #
- Dockerfile Reference — definisi resmi VOLUME dan LABEL serta catatan deprecated MAINTAINER.
- docker volume — CLI Reference — daftar perintah pengelolaan volume termasuk ls dan rm.
- Volumes — konsep volume, volume anonim, dan pola pemasangannya.
- Open Container Initiative — rumah spesifikasi image tempat kunci label baku didefinisikan.