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:

dockerfile
# 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:

dockerfile
# syntax=docker/dockerfile:1
FROM node:22-alpine

Nilai 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:

InstruksiKategoriFungsi singkat
FROMBasisMenentukan image dasar tempat build dimulai
RUNEksekusi buildMenjalankan perintah di dalam layer baru saat build
CMDPerintah runtimePerintah bawaan container, bisa ditimpa argumen docker run
ENTRYPOINTPerintah runtimePerintah utama container; argumen docker run menjadi parameter
COPYBerkasMenyalin berkas atau folder dari build context ke image
ADDBerkasMenyalin seperti COPY plus ekstraksi arsip tar dan unduhan URL
ENVKonfigurasiVariabel environment yang menempel pada image hingga runtime
ARGKonfigurasiParameter build yang hanya hidup selama proses build
WORKDIRStrukturMenetapkan direktori kerja untuk instruksi berikutnya
USERIdentitasMenentukan user yang menjalankan instruksi dan container
EXPOSEMetadataMendokumentasikan port yang didengarkan aplikasi
VOLUMEPenyimpananMenandai titik mount untuk data yang harus bertahan
LABELMetadataMenempelkan pasangan key-value sebagai metadata image
STOPSIGNALRuntimeMengubah sinyal yang dikirim docker stop ke PID 1
HEALTHCHECKRuntimePerintah pemeriksaan kesehatan bawaan image
SHELLEksekusi buildMengganti shell untuk instruksi berbentuk shell form
ONBUILDEksekusi buildMenjadwalkan instruksi yang berjalan saat image dipakai sebagai basis

Mari kita telaah cara membaca peta di atas:

  1. Lima instruksi inti — FROM, WORKDIR, COPY, EXPOSE, dan CMD adalah kelompok yang paling sering dipakai; keempat chapter selanjutnya di section ini membedah COPY/ADD, ENV/ARG, dan CMD/ENTRYPOINT satu per satu sampai tuntas.
  2. Instruksi berperilaku spesifik — EXPOSE, WORKDIR, USER, VOLUME, LABEL, HEALTHCHECK, dan STOPSIGNAL masing-masing punya chapter sendiri di paruh akhir section ini.
  3. Instruksi langka — SHELL dan ONBUILD jarang muncul pada Dockerfile sehari-hari; keduanya kita bahas bersama STOPSIGNAL pada 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.

bash
$ docker history myapp:1.0

Output yang Dihasilkan:

text
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"]     0B

Output di atas terpotong di tengah — baris-baris milik node:22-alpine yang tidak relevan dilewati. Mari kita telaah baris-baris yang tampil:

  1. Empat baris teratas adalah Dockerfile Budi — CMD, EXPOSE, COPY, dan WORKDIR tampil dalam urutan terbalik karena docker history membaca dari langkah termuda; keempatnya persis instruksi Dockerfile kita pada chapter sebelumnya.
  2. Kolom SIZE membedakan dua jenis instruksi — COPY package.json server.js ./ menyumbang 620B berkas nyata, sementara CMD, EXPOSE, dan WORKDIR bernilai 0B karena hanya menulis metadata.
  3. <missing> pada kolom IMAGE — layer-layer di bawah bukan milik image ini melainkan milik node:22-alpine; hanya baris termuda yang memuat ID image myapp:1.0 karena baris itulah yang dibuat saat build kita.
  4. ENTRYPOINT ["/docker-entrypoint.sh"] — baris milik image node yang memperlihatkan pola entrypoint sungguhan; kita bedah perannya pada Chapter CMD dan ENTRYPOINT.
  5. Baris paling bawah — CMD ["/bin/sh"] berasal dari layer Alpine, dasar paling bawah dari node: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:

  1. Basis di puncak karena jarang berubah — FROM hanya ditulis sekali, dan seluruh layer basis langsung dilayani dari cache lokal.
  2. Metadata dan dependensi sebelum kode — bagian yang jarang berubah diletakkan di bawah supaya cache-nya bertahan lama; ketika Budi menyunting server.js lima kali sehari, hanya blok kode dan setelahnya yang dibangun ulang.
  3. Perintah container di ujung — CMD dan ENTRYPOINT murni metadata sehingga aman diletakkan paling akhir, sekaligus menjadi penutup yang alami untuk dibaca.

Peta yang sama bila diterapkan pada myapp terlihat seperti tabel berikut:

BlokIsiWujud pada myapp
1 Basisimage dasarFROM node:22-alpine
2 Metadataidentitas imageLABEL versi="1.1"
3 Dependensipaket yang jarang berubahbelum ada — proyek nyata menaruh RUN npm ci di sini
4 Kodeberkas aplikasi yang sering berubahCOPY package.json server.js ./
5 Konfigurasi runtimeenvironment dan portENV, EXPOSE 8000
6 Perintahcara container menyalaCMD ["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:

dockerfile
RUN apk add --no-cache \
    curl \
    jq \
    git

Builder 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:

dockerfile
# 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:

  1. 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.
  2. LABEL versi="1.1" deskripsi="API kecil milik Budi" — satu instruksi LABEL menempelkan dua metadata sekaligus; nilai versi naik ke 1.1 karena image kini membawa identitas yang lebih kaya, dan label ini kelak kita parameterkan dengan ARG.
  3. Komentar satu baris per blok — setiap blok logis diberi papan nama, mengikuti urutan basis → metadata → kode → konfigurasi runtime → perintah.
  4. Blok dependensi sengaja kosong — myapp tanpa dependensi sehingga tidak ada RUN pemasangan; 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 #