Build pertama pada chapter sebelumnya membuktikan bahwa lima baris Dockerfile sudah cukup untuk melahirkan image myapp:1.0 yang berjalan baik. Namun Dockerfile yang kita tulis masih berupa teka-teki singkat: tanpa komentar, tanpa penjelasan struktur, dan baru menyentuh lima dari seluruh instruksi yang dikenal Dockerfile. Sebelum melangkah lebih jauh ke instruksi-instruksi spesifik, kita perlu melihat peta lengkapnya — ibarat mempelajari tata bahasa sebuah bahasa kecil sebelum menulis kalimat yang panjang. Chapter ini membedah anatomi Dockerfile secara utuh: komentar, parser directive, peta tujuh belas instruksi, pembagian layer versus metadata, sampai konvensi blok logis yang membuat Dockerfile tetap terbaca setelah berbulan-bulan tidak disentuh.
Komentar dalam Dockerfile #
Dockerfile mengenal komentar dengan awalan #, dan builder mengabaikan seluruh barisnya saat membaca resep. Komentar bukan hiasan kosong: pada Dockerfile yang tumbuh panjang, komentar berfungsi sebagai papan nama blok — pembaca berikutnya (termasuk kita sendiri enam bulan kemudian) bisa memahami maksud setiap bagian tanpa harus menebak. Berikut contoh Dockerfile kecil dengan komentar:
# Basis ringan untuk skrip pemeliharaan
FROM alpine:3.20
# Pasang tiga alat kecil dalam satu layer
RUN apk add --no-cache curl jq git
# Perintah bawaan: tampilkan versi curl
CMD ["curl", "--version"]Satu pengecualian penting untuk awalan # adalah parser directive — baris yang bentuknya mirip komentar tetapi justru dibaca sungguh-sungguh oleh builder. Kita bedah direktif tersebut sekarang.
Parser Directive #
Parser directive adalah instruksi khusus yang ditulis dengan format # directive=nilai dan harus menempati baris pertama berkas, sebelum instruksi maupun komentar lainnya. Directive yang paling umum dipakai adalah # syntax:
# syntax=docker/dockerfile:1
FROM node:22-alpineNilai docker/dockerfile:1 menunjuk frontend Dockerfile — komponen BuildKit yang bertugas membaca dan menerjemahkan berkas Dockerfile kita. Tanpa directive ini, BuildKit memakai frontend bawaan yang dibundel bersama daemon, dan versinya bisa lebih tua dari yang kita harapkan. Dengan directive, frontend diambil sebagai image docker/dockerfile:1 dari registry: kanal 1 selalu menunjuk rilis stabil terbaru dari generasi pertama frontend, sehingga kita mendapat perbaikan bug dan fitur baru — misalnya heredoc, COPY --chmod, dan COPY --link — tanpa perlu mengganti Docker Engine.
Peta Tujuh Belas Instruksi #
Setelah directive, isi Dockerfile tersusun dari instruksi-instruksi yang diawali kata kunci. Dockerfile mengenal tujuh belas instruksi, dan tabel berikut memetakan semuanya sekaligus — sebagian akan kita pakai terus-menerus, sebagian baru relevan pada kasus khusus:
| Instruksi | Kategori | Fungsi singkat |
|---|---|---|
FROM | Basis | Menentukan image dasar tempat build dimulai |
RUN | Eksekusi build | Menjalankan perintah di dalam layer baru saat build |
CMD | Perintah runtime | Perintah bawaan container, bisa ditimpa argumen docker run |
ENTRYPOINT | Perintah runtime | Perintah utama container; argumen docker run menjadi parameter |
COPY | Berkas | Menyalin berkas atau folder dari build context ke image |
ADD | Berkas | Menyalin seperti COPY plus ekstraksi arsip tar dan unduhan URL |
ENV | Konfigurasi | Variabel environment yang menempel pada image hingga runtime |
ARG | Konfigurasi | Parameter build yang hanya hidup selama proses build |
WORKDIR | Struktur | Menetapkan direktori kerja untuk instruksi berikutnya |
USER | Identitas | Menentukan user yang menjalankan instruksi dan container |
EXPOSE | Metadata | Mendokumentasikan port yang didengarkan aplikasi |
VOLUME | Penyimpanan | Menandai titik mount untuk data yang harus bertahan |
LABEL | Metadata | Menempelkan pasangan key-value sebagai metadata image |
STOPSIGNAL | Runtime | Mengubah sinyal yang dikirim docker stop ke PID 1 |
HEALTHCHECK | Runtime | Perintah pemeriksaan kesehatan bawaan image |
SHELL | Eksekusi build | Mengganti shell untuk instruksi berbentuk shell form |
ONBUILD | Eksekusi build | Menjadwalkan instruksi yang berjalan saat image dipakai sebagai basis |
Mari kita telaah cara membaca peta di atas:
- Lima instruksi inti —
FROM,WORKDIR,COPY,EXPOSE, danCMDadalah kelompok yang paling sering dipakai; keempat chapter selanjutnya di section ini membedahCOPY/ADD,ENV/ARG, danCMD/ENTRYPOINTsatu per satu sampai tuntas. - Instruksi berperilaku spesifik —
EXPOSE,WORKDIR,USER,VOLUME,LABEL,HEALTHCHECK, danSTOPSIGNALmasing-masing punya chapter sendiri di paruh akhir section ini. - Instruksi langka —
SHELLdanONBUILDjarang muncul pada Dockerfile sehari-hari; keduanya kita bahas bersamaSTOPSIGNALpada Chapter Instruksi Lanjutan.
Instruksi Layer dan Instruksi Metadata #
Dari tujuh belas instruksi tersebut, hanya tiga yang mengubah isi filesystem image: RUN, COPY, dan ADD. Ketiganya menghasilkan tepat satu layer baru berisi diff terhadap layer di bawahnya — hasil RUN apk add berisi paket yang dipasang, hasil COPY berisi berkas yang disalin. Empat belas instruksi sisanya hanya menulis metadata pada konfigurasi image: ENV mencatat variabel, EXPOSE mencatat port, LABEL mencatat pasangan key-value, dan seterusnya. Langkah-langkah seperti ini tetap tampil sebagai langkah build, tetapi layer yang dihasilkannya kosong — nol berkas — persis pembagian yang sudah kita bedah pada Chapter Konsep Image Layer.
Pembedaan ini bukan pengetahuan kosmetik. Karena layer adalah unit cache dan unit distribusi, instruksi yang menghasilkan layer mahal harus diperlakukan berbeda dari instruksi metadata yang gratis: memindahkan LABEL atau ENV hampir tidak berbiaya, sedangkan setiap perubahan pada RUN dan COPY menghasilkan layer baru yang harus dibangun, disimpan, dan diunggah. Konsekuensi urutannya kita bedah mendalam pada Chapter Build Cache di Section Image Build dan Optimisasi — untuk sekarang, cukup satu alat sederhana untuk melihat sendiri pembagian ini.
$ docker history myapp:1.0Output yang Dihasilkan:
IMAGE CREATED CREATED BY SIZE COMMENT
05303e530e6f 2 hours ago CMD ["node" "server.js"] 0B
<missing> 2 hours ago EXPOSE 8000 0B
<missing> 2 hours ago COPY package.json server.js ./ 620B
<missing> 2 hours ago WORKDIR /app 0B
<missing> 3 weeks ago CMD ["node"] 0B
<missing> 3 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
...
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0BOutput di atas terpotong di tengah — baris-baris milik node:22-alpine yang tidak relevan dilewati. Mari kita telaah baris-baris yang tampil:
- Empat baris teratas adalah Dockerfile Budi —
CMD,EXPOSE,COPY, danWORKDIRtampil dalam urutan terbalik karenadocker historymembaca dari langkah termuda; keempatnya persis instruksi Dockerfile kita pada chapter sebelumnya. - Kolom SIZE membedakan dua jenis instruksi —
COPY package.json server.js ./menyumbang 620B berkas nyata, sementaraCMD,EXPOSE, danWORKDIRbernilai 0B karena hanya menulis metadata. <missing>pada kolom IMAGE — layer-layer di bawah bukan milik image ini melainkan miliknode:22-alpine; hanya baris termuda yang memuat ID imagemyapp:1.0karena baris itulah yang dibuat saat build kita.ENTRYPOINT ["/docker-entrypoint.sh"]— baris milik image node yang memperlihatkan pola entrypoint sungguhan; kita bedah perannya pada Chapter CMD dan ENTRYPOINT.- Baris paling bawah —
CMD ["/bin/sh"]berasal dari layer Alpine, dasar paling bawah darinode:22-alpine, yang totalnya menyumbang sekitar 7,8MB sebelum biner Node.js ditumpuk di atasnya.
Blok Logis Dockerfile #
Dockerfile yang baik tidak sekadar valid — ia tersusun mengikuti urutan blok yang sudah menjadi konsensus komunitas. Urutannya dari atas ke bawah: basis, metadata, dependensi, kode aplikasi, konfigurasi runtime, lalu perintah container:
Diagram di atas menunjukkan alur baku penyusunan Dockerfile. Mari kita telaah alasannya:
- Basis di puncak karena jarang berubah —
FROMhanya ditulis sekali, dan seluruh layer basis langsung dilayani dari cache lokal. - Metadata dan dependensi sebelum kode — bagian yang jarang berubah diletakkan di bawah supaya cache-nya bertahan lama; ketika Budi menyunting
server.jslima kali sehari, hanya blok kode dan setelahnya yang dibangun ulang. - Perintah container di ujung —
CMDdanENTRYPOINTmurni metadata sehingga aman diletakkan paling akhir, sekaligus menjadi penutup yang alami untuk dibaca.
Peta yang sama bila diterapkan pada myapp terlihat seperti tabel berikut:
| Blok | Isi | Wujud pada myapp |
|---|---|---|
| 1 Basis | image dasar | FROM node:22-alpine |
| 2 Metadata | identitas image | LABEL versi="1.1" |
| 3 Dependensi | paket yang jarang berubah | belum ada — proyek nyata menaruh RUN npm ci di sini |
| 4 Kode | berkas aplikasi yang sering berubah | COPY package.json server.js ./ |
| 5 Konfigurasi runtime | environment dan port | ENV, EXPOSE 8000 |
| 6 Perintah | cara container menyala | CMD ["node", "server.js"] |
Kelanjutan Baris untuk Instruksi Panjang #
Instruksi yang panjang bisa dipecah menjadi beberapa baris dengan karakter garis miring terbalik \ di akhir setiap baris — sama seperti kelanjutan baris pada shell. Pola ini paling sering dipakai pada RUN yang memasang banyak paket atau COPY dengan banyak sumber:
RUN apk add --no-cache \
curl \
jq \
gitBuilder membaca baris-baris tersebut sebagai satu instruksi RUN apk add --no-cache curl jq git, sehingga hasilnya tetap satu layer tunggal — memecah baris tidak memecah layer. Manfaatnya murni keterbacaan: satu paket per baris membuat daftar mudah ditinjau dan diff-nya bersih saat satu paket ditambah atau dibuang.
Dockerfile myapp Versi Rapi #
Menggabungkan seluruh konvensi di atas, Budi merapikan Dockerfile myapp menjadi bentuk yang akan dibawa terus sepanjang section ini — parser directive di baris pertama, komentar penanda blok, dan urutan blok logis:
# syntax=docker/dockerfile:1
# Basis: runtime Node.js 22 di atas Alpine
FROM node:22-alpine
# Metadata image
LABEL versi="1.1" deskripsi="API kecil milik Budi"
# Direktori kerja untuk semua instruksi berikutnya
WORKDIR /app
# Berkas aplikasi disalin dari build context
COPY package.json server.js ./
# Port yang didengarkan aplikasi
EXPOSE 8000
# Perintah bawaan saat container berjalan
CMD ["node", "server.js"]Mari kita telaah perubahan dari Dockerfile lima baris pada chapter sebelumnya:
- Baris pertama
# syntax=docker/dockerfile:1— parser directive yang memastikan frontend terbaru membaca berkas ini; komentar-komentar di bawahnya kini aman karena directive sudah berada di posisi paling atas. LABEL versi="1.1" deskripsi="API kecil milik Budi"— satu instruksiLABELmenempelkan dua metadata sekaligus; nilai versi naik ke 1.1 karena image kini membawa identitas yang lebih kaya, dan label ini kelak kita parameterkan denganARG.- Komentar satu baris per blok — setiap blok logis diberi papan nama, mengikuti urutan basis → metadata → kode → konfigurasi runtime → perintah.
- Blok dependensi sengaja kosong — myapp tanpa dependensi sehingga tidak ada
RUNpemasangan; pada proyek berdependensi, blok inilah yang menampung instalasi paket agar cache-nya tidak terganggu perubahan kode.
Dari lima baris polos, Dockerfile myapp kini menjadi sembilan belas baris yang rapi dan berterangkan — dan setiap barisnya kini kita pahami posisinya dalam peta besar. Namun peta belum selesai: dari tujuh belas instruksi, dua yang paling sering menyentuh berkas adalah COPY dan ADD, dan keduanya sering dianggap kembar padahal perilakunya berbeda penting. Salah pilih di antara keduanya bisa membuat image membawa berkas yang tidak perlu atau build yang tak terkendali — perbedaan itulah yang kita bedah pada Chapter COPY dan ADD.
Referensi #
- Dockerfile Reference — format berkas, komentar, parser directive, dan definisi resmi tiap instruksi.
- Dockerfile best practices — konvensi urutan instruksi dan penyusunan blok logis.
- docker image history — CLI Reference — flag lengkap untuk membaca riwayat layer image.