Chapter sebelumnya menutup dengan satu kebutuhan yang belum terjawab: image yang sama perlu berperilaku berbeda — berbeda antara saat dibangun dan saat dijalankan, berbeda antara versi satu dan versi dua. Kebutuhan itu dijawab Dockerfile lewat sepasang instruksi yang tampak serupa namun hidup di dua dunia berbeda: ENV menetapkan variabel yang menempel pada image hingga container menyala, sedangkan ARG menerima parameter yang hanya hidup selama proses build berlangsung. Budi akan memakai keduanya sekaligus pada myapp — mode aplikasi lewat ENV, nomor versi lewat ARG — dan di sepanjang jalan kita akan menyaksikan jejak masing-masing instriksi pada metadata image, termasuk satu jejak yang wajib membuat kita berhati-hati dengan rahasia.

ENV: Variabel yang Menempel pada Image #

Instruksi ENV NODE_ENV=production menulis satu pasangan key-value ke konfigurasi image. Konsekuensinya menjalar ke dua dunia sekaligus: setiap instruksi Dockerfile setelah baris itu dapat membaca nilainya saat build, dan setiap container yang lahir dari image tersebut mewarisi variabel itu di environment-nya saat berjalan. Dockerfile myapp kini berbentuk berikut — perhatikan bahwa nomor versi tidak lagi ditulis mati pada LABEL, melainkan diambil dari ARG yang kita bedah di bagian berikutnya:

dockerfile
# syntax=docker/dockerfile:1

# Basis: runtime Node.js 22 di atas Alpine
FROM node:22-alpine

# Versi sebagai parameter build dengan nilai bawaan
ARG VERSI=1.0

# Metadata image — versi diambil dari parameter build
LABEL version=$VERSI deskripsi="API kecil milik Budi"

# Mode aplikasi: berlaku saat build dan saat container berjalan
ENV NODE_ENV=production

# Direktori kerja untuk semua instruksi berikutnya
WORKDIR /app

# Berkas aplikasi disalin dari build context
COPY package.json server.js ./

# Cap mode dan versi pada berkas kecil yang bisa dibaca dari container
RUN echo "MODE: ${NODE_ENV} / VERSI: ${VERSI}" > /app/mode.txt

# Port yang didengarkan aplikasi
EXPOSE 8000

# Perintah bawaan saat container berjalan
CMD ["node", "server.js"]

Mari kita telaah dua baris yang baru di atas:

  1. ENV NODE_ENV=production — menetapkan variabel NODE_ENV, konvensi ekosistem Node.js yang dibaca framework seperti Express untuk memilih perilaku produksi. Nilainya menempel pada image, bukan pada satu instruksi tertentu.
  2. RUN echo "MODE: ${NODE_ENV} / VERSI: ${VERSI}" > /app/mode.txt — instruksi RUN berjalan di dalam shell yang environment-nya sudah memuat seluruh ENV dan ARG yang berlaku pada titik itu; shell meresolusi kedua variabel saat perintah dieksekusi, dan hasilnya membeku sebagai berkas pada layer. Karena RUN menghasilkan layer — satu dari tiga instruksi pembentuk isi dari Chapter Struktur Dockerfile — berkas mode.txt ini menjadi bukti permanen di dalam image.

ARG: Parameter yang Hanya Hidup Saat Build #

ARG VERSI=1.0 mendeklarasikan parameter build bernama VERSI dengan nilai bawaan 1.0. Nilai bawaan itu hanya cadangan — saat build dijalankan, nilai dapat diganti lewat flag --build-arg tanpa menyentuh satu baris Dockerfile pun. Budi membangun versi 2.0 dari myapp:

bash
$ docker build --build-arg VERSI=2.0 -t myapp:2.0 .

Output yang Dihasilkan:

text
[+] Building 1.2s (13/13) FINISHED
 => [internal] load build definition from Dockerfile                    0.1s
 => => transferring dockerfile: 638B                                    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 version=2.0 deskripsi=API kecil milik Budi              0.0s
 => [3/7] ENV NODE_ENV=production                                       0.0s
 => [4/7] WORKDIR /app                                                  0.0s
 => [5/7] COPY package.json server.js ./                                0.1s
 => [6/7] RUN echo "MODE: ${NODE_ENV} / VERSI: ${VERSI}" > /app/mode.txt 0.0s
 => [7/7] EXPOSE 8000                                                   0.0s
 => exporting layers                                                   0.2s
 => => exporting layers                                                0.1s
 => => writing image sha256:9f4c8e2b1d7a3e5f7a9c1b3d5f7a9c1e3b5d7f2a4c6e8b0d2f4a6c8e0b2d4f6a8 0.1s
 => => naming to docker.io/library/myapp:2.0                            0.0s

Mari kita telaah jejak nilai 2.0 pada output di atas:

  1. --build-arg VERSI=2.0 — nilai dari command line mengalahkan bawaan 1.0 di Dockerfile; tanpa flag ini, docker build -t myapp:2.0 . tetap berhasil tetapi label-nya akan tercatat version=1.0 — ketidakcocokan tag dan label yang justru menjadi alat audit: label berkata jujur tentang apa yang terjadi saat build.
  2. [2/7] LABEL version=2.0 deskripsi=... — BuildKit menampilkan instruksi setelah substitusi; $VERSI telah diganti nilainya pada baris yang dieksekusi. Kunci version menggantikan versi yang kita pakai pada Chapter Struktur Dockerfile — nama kunci LABEL bebas dipilih, dan bentuk lowercase Inggris ini lebih akrab dibaca tool pemindai image.
  3. [6/7] RUN echo ... — berjalan 0.0 detik dan menghasilkan layer berisi mode.txt; teks perintahnya masih memuat kedua variabel dalam bentuk ${...} karena resolusi terjadi di dalam shell saat eksekusi.

Bukti bahwa kedua instruksi benar-benar bekerja di dua dunianya masing-masing bisa dibaca langsung dari dalam image:

bash
$ docker run --rm myapp:2.0 cat /app/mode.txt

Output yang Dihasilkan:

text
MODE: production / VERSI: 2.0
bash
$ docker run -d --name myapp-env myapp:2.0
$ docker exec myapp-env printenv NODE_ENV

Output yang Dihasilkan:

text
b8e3a7c2d4f5691e0a8b2c4d6e8f0a1b3c5d7e9f1a3b5c7d9e0f2a4b6c8d0e2f4a
production

Dua hasil di atas memperlihatkan pembagian kerja yang tegas. mode.txt adalah bukti dunia build: kedua variabel tersedia bagi RUN, lalu hasil resolusinya membeku permanen di layer. printenv NODE_ENV adalah bukti dunia runtime: container yang baru saja dinyalakan mewarisi variabel itu tanpa perlu satu instruksi apa pun — dan nilai VERSI justru tidak lagi bisa ditanya dari container, karena ARG sudah tamat tugasnya begitu build selesai.

ENV versus ARG #

Kedua instruksi kini punya bukti kerja masing-masing, dan tabel berikut merangkum lima aspek yang membedakan mereka:

AspekENVARG
Kapan hidupsaat build dan selama container berjalanhanya selama proses build
Nilai bawaanditulis langsung di Dockerfileditulis di Dockerfile, bisa ditimpa --build-arg
Cara mengganti nilaiflag -e saat docker run — materi Chapter Environment Variable di Section Konfigurasi Containerflag --build-arg saat docker build
Jejak pada imageterlihat pada docker image inspect field .Config.Envtercatat pada docker history lewat notasi ARG di baris RUN
Kasus pakai khaskonfigurasi aplikasi lintas environment: NODE_ENV, PATHparameter build: nomor versi, pilihan varian, alamat proxy

Aturan memilihnya satu kalimat: bila nilainya perlu dibaca aplikasi saat container berjalan, gunakan ENV; bila nilainya hanya dipakai untuk merakit isi image, gunakan ARG — dan biarkan image yang dihasilkan tetap sama untuk semua orang yang membangunnya.

Jejak ENV dan ARG pada Metadata Image #

Kedua instruksi meninggalkan jejak yang bisa dibaca siapa pun yang memegang image — dan inilah bagian yang menuntut kehati-hatian. Jejak ENV terbaca dari field konfigurasi yang sudah kita kenal pada Chapter Image Inspect dan History:

bash
$ docker image inspect myapp:2.0 --format '{{json .Config.Env}}'

Output yang Dihasilkan:

text
["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NODE_ENV=production"]

Variabel NODE_ENV duduk berdampingan dengan PATH bawaan image basis — seluruh daftar ini diwarisi setiap container dari image tersebut. Adapun jejak ARG muncul pada riwayat build:

bash
$ docker history myapp:2.0

Output yang Dihasilkan:

text
IMAGE          CREATED         CREATED BY                                          SIZE     COMMENT
9f4c8e2b1d7a   2 minutes ago   CMD ["node" "server.js"]                            0B
<missing>      2 minutes ago   EXPOSE 8000                                         0B
<missing>      2 minutes ago   RUN |1 VERSI=2.0 echo "MODE: ${NODE_ENV} / VERS…    34B
<missing>      2 minutes ago   COPY package.json server.js ./                      620B
<missing>      2 minutes ago   WORKDIR /app                                        0B
<missing>      2 minutes ago   ENV NODE_ENV=production                             0B
<missing>      2 minutes ago   LABEL version=2.0 deskripsi=API kecil milik Budi    0B
...

Mari kita telaah jejak-jejak tersebut:

  1. ENV NODE_ENV=production sebagai baris tersendiri — bernilai 0B karena murni metadata, dan nilainya utuh terpampang.
  2. LABEL version=2.0 ... — nilai hasil substitusi ARG tercatat permanen; siapa pun yang membaca history tahu image ini dibangun sebagai versi 2.0.
  3. RUN |1 VERSI=2.0 echo ... — notasi |1 mencatat bahwa instruksi RUN memakai satu ARG beserta nilainya; teks perintahnya sendiri masih memuat ${VERSI} yang belum terurai, tetapi nilai aslinya tercetak dengan terang-terangan.

Scope ARG: Satu Stage Satu Deklarasi #

Perilaku ARG yang paling sering mengejutkan muncul pada Dockerfile multi-stage: deklarasi ARG berlaku per stage. ARG yang ditulis sebelum baris FROM pertama hanya bisa dipakai oleh baris FROM itu sendiri, dan setiap stage harus mendeklarasikan ulang ARG yang ingin dibacanya:

dockerfile
ARG NODE_VER=22
FROM node:${NODE_VER}-alpine AS builder
ARG VERSI=1.0
RUN echo "membangun versi ${VERSI}"

FROM alpine:3.20
RUN echo "membangun versi ${VERSI}"

Mari kita telaah akibat susunan di atas: baris ARG NODE_VER=22 di puncak hanya berlaku untuk substitusi ${NODE_VER} pada baris FROM — satu-satunya tempat variabel sebelum-FROM bisa dipakai. ARG VERSI=1.0 di dalam stage builder membuat RUN pertama berjalan mulus dengan nilai 1.0. Namun RUN pada stage kedua gagal dengan galat VERSI: unbound variable — stage yang berakar pada alpine:3.20 adalah dunia tersendiri yang tidak mewarisi deklarasi ARG dari stage lain, dan --build-arg VERSI=2.0 di command line tidak mengubah keadaan itu selama deklarasinya tidak diulang. Mekanisme stage itu sendiri kita kuasai tuntas pada Chapter Multi-Stage Build di Section Image Build dan Optimisasi.

Dua instruksinya kini lengkap: ENV membawa konfigurasi yang hidup sampai container menyala, ARG membawa parameter yang menyempurnakan resep saat build. Satu pertanyaan besar di Dockerfile myapp masih menggantung: siapa yang sebenarnya menyalakan aplikasi saat container lahir — dan apa yang terjadi bila argumen docker run menyusul setelahnya. Jawabannya berada di sepasang instruksi terpenting di dunia Dockerfile, CMD dan ENTRYPOINT, sekaligus pilihan gaya penulisannya yang menentukan nasib sinyal docker stop.

Referensi #