HEALTHCHECK menutup instruksi runtime yang paling terasa dampaknya: image myapp kini melaporkan kesehatannya sendiri dan kolom STATUS berhenti menyampaikan kabar baik yang palsu. Tiga instruksi terakhir di peta tujuh belas jarang muncul di Dockerfile sehari-hari, tetapi masing-masing menyimpan pelajaran yang pantas dipahami: STOPSIGNAL mengubah sinyal yang dikirim docker stop, SHELL mengganti program perantara untuk bentuk shell form, dan ONBUILD menyimpan instruksi yang baru menyala ketika image kita dipakai orang lain sebagai basis. Setelah ketiganya kita bedah, kita rangkai seluruh section dengan satu Dockerfile final untuk myapp — gabungan seluruh pola yang telah kita kumpulkan sepanjang sepuluh chapter.
STOPSIGNAL Mengubah Sinyal Berhenti #
Mekanisme docker stop sudah kita bedah pada Chapter Menghentikan dan Menjalankan Container: daemon mengirim SIGTERM ke PID 1, menunggu dengan masa tenggang 10 detik, lalu menurunkan SIGKILL bila proses tetap ngotot. Catatan pada chapter itu juga sempat menyinggung bahwa sinyal pertama bisa ditimpa oleh image — dan instruksinya adalah STOPSIGNAL. Contoh paling terkenal ada pada image nginx, yang sudah lama tinggal di mesin latihan kita:
$ docker image inspect nginx:alpine --format '{{.Config.StopSignal}}'Output yang Dihasilkan:
SIGQUITMari kita telaah makna satu kata di atas:
SIGQUIT, bukanSIGTERM— image nginx (varian alpine yang kita pakai menunjuk rilis 1.29) menetapkan sinyal berhenti sendiri; setiapdocker stoppada container nginx mengirimSIGQUITkepada master prosesnya.- Jalur shutdown yang lebih rapi milik nginx — master nginx menanggapi
SIGQUITdengan mematikan para worker secara berurutan sambil menyelesaikan permintaan yang sedang dilayani — inilah shutdown graceful versi nginx;SIGTERMjustru memotong lebih cepat. - Masa tenggang tetap berlaku —
STOPSIGNALhanya mengganti sinyal yang dikirim, bukan mekanisme tunggunya; lewat 10 detik tanpa hasil,SIGKILLtetap turun.
Kapan aplikasi kita membutuhkan instruksinya? Node menangani SIGTERM dengan baik sehingga myapp nyaman dengan nilai bawaan. Pola pemakaiannya umumnya dua arah: aplikasi yang jalur bersihnya berada pada sinyal lain — misalnya rutinitas pembersihan SIGTERM yang lambat sementara SIGINT membawa aplikasi ke jalur cepat yang tetap menutup koneksi dengan rapi — atau image yang ingin menuliskan kontrak secara eksplisit. Bentuk sintaksnya STOPSIGNAL SIGQUIT dengan nilai berupa nama sinyal atau angka; SIGTERM misalnya adalah angka 15.
SHELL untuk Bentuk Shell Form #
Setiap instruksi yang ditulis dalam bentuk shell form — RUN apk add curl, CMD node server.js — tidak dieksekusi langsung oleh builder. Builder memanggil program perantara: di image berbasis Linux, perantaranya adalah ["/bin/sh", "-c"]. SHELL mengganti perantara itu untuk semua instruksi shell form setelah barisnya. Kebutuhan paling klasiknya ada di image dasar Windows yang perantaranya ["cmd", "/S", "/C"] — pola lama menggantinya dengan PowerShell:
SHELL ["powershell", "-Command"]
RUN Get-DateMari kita telaah blok kecil di atas:
- Bentuk wajib exec form — nilai SHELL ditulis sebagai array JSON; instruksi ini tidak menerima bentuk shell.
RUN Get-Datekini dieksekusi PowerShell — tanpa SHELL, baris yang sama akan dijalankancmd; hasilnya baris perintah PowerShell menjadi sah dipakai pada instruksi berikutnya.- Pemakaian di Linux lebih jarang — kebutuhan khasnya misalnya mengaktifkan
pipefailpada image Debian denganSHELL ["/bin/bash", "-o", "pipefail", "-c"], supaya kegagalan di tengah pipa perintah panjang tidak tertelan oleh sukses perintah terakhir. - Exec form tidak tersentuh —
CMD ["node", "server.js"]tetap dieksekusi langsung tanpa perantara; SHELL hanya mengurus bentuk shell form.
ONBUILD Menyimpan Instruksi untuk Image Anak #
ONBUILD adalah satu-satunya instruksi yang tidak berbuat apa pun ketika image miliknya dibangun. ONBUILD COPY . . hanya menyimpan pemicu (trigger) pada metadata image. Pemicu itu baru menyala ketika image lain dibangun dengan FROM menunjuk image ini — instruksi yang tersimpan dieksekusi seolah-olah ditulis di Dockerfile si anak:
FROM node:22-alpine
WORKDIR /app
ONBUILD COPY package.json server.js ./
ONBUILD CMD ["node", "server.js"]Dockerfile anak cukup satu baris:
FROM myapp:indukBuild image anak akan menjalankan COPY package.json server.js ./ dan CMD ["node", "server.js"] yang tersimpan di metadata induk — Dockerfile anak tidak menampilkan instruksi itu sama sekali. Pola semacam ini dahulu populer untuk image dasar framework: pembuat framework menyalakan salinan kode otomatis di setiap proyek turunan.
Rekap Tujuh Belas Instruksi #
Sepuluh chapter section ini telah menyusuri hampir seluruh instruksi Dockerfile. Tabel berikut merangkum semuanya — kapan dipakai dan jebakan yang paling sering menjebak — sebagai peta penutup sebelum kita merakit Dockerfile final:
| Instruksi | Kapan dipakai | Jebakan utama |
|---|---|---|
| FROM | Setiap Dockerfile, awal tiap stage | Tag mengambang latest bisa diam-diam berpindah isi |
| RUN | Memasang paket dan mengubah filesystem saat build | Satu instruksi satu layer; cache paket dibersihkan di instruksi yang sama |
| COPY | Menyalin berkas dari build context | COPY . . membawa berkas asing tanpa .dockerignore |
| ADD | Ekstraksi arsip tar lokal otomatis | Perilaku implisit; jangan dipakai mengunduh URL |
| ENV | Variabel yang menempel hingga runtime | Terbaca docker inspect; jangan menyimpan rahasia |
| ARG | Parameter build yang berubah per build | Nilai terekam di history; bisa bocor ke siapa saja |
| CMD | Perintah bawaan yang mudah ditimpa | Shell form tidak meneruskan sinyal ke aplikasi |
| ENTRYPOINT | Perintah utama container yang stabil | Lupa exec form membuat sinyal berhenti di shell |
| WORKDIR | Menetapkan direktori kerja instruksi berikutnya | Tanpa WORKDIR, berkas mendarat di / |
| USER | Identitas non-root untuk build dan runtime | User harus dibuat dulu; urutan setelah instalasi paket |
| EXPOSE | Dokumentasi port aplikasi | Tidak menerbitkan port — miskonsepsi klasik |
| VOLUME | Menandai titik data layak-pisah | Volume anonim menumpuk diam-diam |
| LABEL | Metadata key-value, standar OCI | MAINTAINER deprecated; kunci bebas tak dibaca tool |
| HEALTHCHECK | Probe kesehatan bawaan image | Alat probe harus ada di dalam image |
| STOPSIGNAL | Mengubah sinyal docker stop | Aplikasi harus benar-benar menangani sinyal itu |
| SHELL | Mengganti perantara shell form | Hanya menyentuh bentuk shell form |
| ONBUILD | Instruksi yang menyala saat jadi basis | Membingungkan; multi-stage lebih baik |
Dockerfile Final myapp 2.0 #
Saatnya merakit semuanya. Budi menyusun Dockerfile final yang menggabungkan seluruh pola section ini — parser directive, basis terpin, label OCI, WORKDIR, environment, user non-root, salinan manifest-dulu, healthcheck, sinyal berhenti eksplisit, dan CMD exec form — lalu menaikkan versi menjadi 2.0:
# syntax=docker/dockerfile:1
# Basis dipin eksplisit: 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="2.0" \
org.opencontainers.image.authors="Budi <[email protected]>" \
org.opencontainers.image.source="git.example.com/budi/myapp" \
org.opencontainers.image.licenses="MIT"
# Direktori kerja semua instruksi berikutnya
WORKDIR /app
# Environment runtime aplikasi
ENV NODE_ENV=production
# User non-root dibuat selagi build masih berjalan sebagai root
RUN addgroup -g 1000 app && adduser -u 1000 -G app -H -D app
# Manifest dulu supaya cache instalasi bertahan saat kode berubah
COPY --chown=1000:1000 package.json ./
# Kode aplikasi paling belakang karena paling sering berubah
COPY --chown=1000:1000 server.js ./
# Semua instruksi selanjutnya berjalan sebagai user app
USER app
# Port yang didengarkan aplikasi
EXPOSE 8000
# Probe kesehatan bawaan image
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- localhost:8000/ || exit 1
# Sinyal berhenti yang diharapkan aplikasi
STOPSIGNAL SIGTERM
# Perintah bawaan container, exec form
CMD ["node", "server.js"]Mari kita telaah Dockerfile final tersebut blok demi blok:
# syntax=docker/dockerfile:1— parser directive yang memastikan frontend Dockerfile modern membaca berkas ini; posisinya wajib di baris paling atas, persis aturan dari Chapter Struktur Dockerfile.FROM node:22-alpine— basis dipin ke versi mayor Node 22 di atas Alpine, bukan tag mengambang; bila ingin kaku total, tag bisa dikunci lebih jauh dengan digest — materi Chapter Image Digest.LABEL org.opencontainers.image.*— enam pasangan metadata standar OCI menggantikan label bebas; title, version, dan source terbaca registry serta tool audit.WORKDIR /appdanENV NODE_ENV=production— alamat rumah dan environment produksi untuk seluruh instruksi setelahnya.RUN addgroup ... && adduser ...— userappUID 1000 dibuat selagi build masih root; seluruh pekerjaan ber-akses-root berakhir di baris ini.COPY --chown=1000:1000 package.json ./laluserver.js ./— salinan manifest dulu dan kode belakang, pola dari Chapter COPY dan ADD; flag--chownmemindahkan kepemilikan ke UID 1000 tanpa layer tambahan, menyambung konsistensi UID dari Chapter WORKDIR dan USER.USER app— sejak baris ini, instruksi apa pun — termasuknode server.jssaat container menyala — berjalan tanpa wewenang root.EXPOSE 8000— dokumentasi port yang kini kita tulis dengan kesadaran penuh bahwa penerbitan tetap urusan-pdan-Psaat runtime.HEALTHCHECK ... wget -qO- localhost:8000/ || exit 1— probe bawaan image dengan interval 30 detik, timeout 3 detik, masa hangat 5 detik, dan 3 percobaan; exit code 0 melaporkan sehat, 1 melaporkan sakit.STOPSIGNAL SIGTERM— kontrak eksplisit bahwa aplikasi ini menantiSIGTERM; nilainya sama dengan bawaan, tetapi penulisan eksplisitnya memberi tahu pembaca image bahwa sinyal ini memang disengaja.CMD ["node", "server.js"]— perintah bawaan dalam exec form, bentuk yang meneruskan sinyal langsung ke proses node.
Budi membangunnya:
$ docker build -t myapp:2.0 .Output yang Dihasilkan:
[+] Building 4.8s (18/18) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 1301B 0.0s
=> [internal] load metadata for docker.io/library/node:22-alpine 0.7s
=> [1/11] FROM docker.io/library/node:22-alpine 0.0s
=> => resolve docker.io/library/node:22-alpine 0.7s
=> [2/11] LABEL org.opencontainers.image.title=myapp ... 0.0s
=> [3/11] WORKDIR /app 0.0s
=> [4/11] ENV NODE_ENV=production 0.0s
=> [5/11] RUN addgroup -g 1000 app && adduser -u 1000 -G app -H -D app 0.5s
=> [6/11] COPY --chown=1000:1000 package.json ./ 0.1s
=> => [7/11] COPY --chown=1000:1000 server.js ./ 0.1s
=> => transferring context: 1958B 0.0s
=> [8/11] USER app 0.0s
=> [9/11] EXPOSE 8000 0.0s
=> [10/11] HEALTHCHECK &{["CMD-SHELL" "wget -qO- localhost:8000/ || exit 1"] 30s 3s 5s 0s 3 false} 0.0s
=> [11/11] STOPSIGNAL SIGTERM 0.0s
=> exporting to image 0.2s
=> => exporting layers 0.1s
=> => writing image sha256:d94c1f8b2a6e7b0d4c9f2a5e8b1d3f6c0a7e9b2d4f8a1c5e3b7d9f0a2c4e6b8d 0.1s
=> => naming to docker.io/library/myapp:2.0 0.0sMari kita telaah beberapa kejadian penting pada build di atas:
- Seluruh rantai dieksekusi ulang sejak LABEL — isi label berubah karena versi naik 1.6 menjadi 2.0, dan perubahan satu instruksi membatalkan cache semua instruksi setelahnya dalam stage yang sama, persis pola yang kita saksikan pada Chapter COPY dan ADD; cara menyusun Dockerfile agar dampaknya minimal adalah materi Chapter Build Cache.
transferring context: 1958B— berkascatatan.txtpeninggalan eksperimen lama masih menetap di folder Budi dan ikut terkirim ke builder; nilainya tidak masuk image karena COPY menyebut berkas secara eksplisit, dan cara menyaring context lebih ketat menanti Chapter .dockerignore.- COPY dipecah dua langkah — manifest di
[6/11], kode di[7/11]; begituserver.jsberubah, hanya langkah itu dan sesudahnya yang dibangun ulang. HEALTHCHECKdanSTOPSIGNALtampil sebagai langkah — keduanya instruksi metadata yang tetap dihitung sebagai langkah build, sementaraCMDseperti biasa dicatat saat ekspor tanpa nomor langkah.
Waktu pengujian tiba. Budi membuang container lama supaya port 8000 kosong, lalu menjalankan image final:
$ docker run -d --name myapp-final -p 8000:8000 myapp:2.0Output yang Dihasilkan:
8e2b7d4a1c9f0a5b3d6e8c1a4f7b2d9e5c0a3b6d8f2a4c7e1b5d9f0a3c6e8b1a$ curl localhost:8000Output yang Dihasilkan:
{"app":"myapp","pesan":"Halo dari container"}Setelah interval pertama berlalu, seluruh janji Dockerfile ini terbaca pada satu baris status:
$ docker psOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8e2b7d4a1c9f myapp:2.0 "node server.js" 41 seconds ago Up 40 seconds (healthy) 0.0.0.0:8000->8000/tcp, [::]:8000->8000/tcp myapp-finalDua pemeriksaan kecil menutup verifikasi — identitas proses dan kontrak sinyal:
$ docker exec myapp-final whoamiOutput yang Dihasilkan:
app$ docker image inspect myapp:2.0 --format '{{.Config.StopSignal}}'Output yang Dihasilkan:
SIGTERMPerjalanan panjang section ini bermula dari satu Dockerfile lima baris yang kita tulis begitu saja; kini setiap baris Dockerfile final memiliki alasan yang bisa kita jelaskan — dari directive hingga exec form. Sebagai penutup yang manis, koleksi image myapp di mesin Budi kini membentuk garis waktu perjalanan itu:
$ docker images myappOutput yang Dihasilkan:
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp 2.0 d94c1f8b2a6e 30 seconds ago 130MB
myapp 1.6 a2f74c9e3b51 25 minutes ago 130MB
myapp 1.5 4e8d1a6c92f7 About an hour ago 130MB
myapp 1.4 7b3e9f2c81d4 2 hours ago 130MB
...Output di atas terpotong — tag-tag myapp lain dari perjalanan section ini tidak ditampilkan. Namun menulis Dockerfile hanyalah separuh keterampilan; separuh lainnya adalah memahami mesin yang mengeksekusinya. Mengapa seluruh isi context dikirim ke builder padahal hanya sebagian kecil disalin, bagaimana cache dihitung dan kapan ia batal, bagaimana multi-stage memangkas image produksi menjadi sepersekian ukurannya — semuanya itu adalah urusan Section Image Build dan Optimisasi, yang membuka dengan Chapter docker build.
Referensi #
- Dockerfile Reference — definisi resmi STOPSIGNAL, SHELL, dan ONBUILD.
- Dockerfile best practices — panduan penyusunan Dockerfile produksi yang menjadi acuan blok final kita.
- nginx — Docker Hub — contoh image nyata yang menetapkan STOPSIGNAL SIGQUIT.