Dockerfile adalah berkas teks yang mendefinisikan cara sebuah image dibangun, mulai dari image dasar yang dipakai hingga perintah default saat container berjalan. Topik ini membedah satu Dockerfile utuh untuk aplikasi catatan berbasis pustaka standar Python, baris demi baris, dengan fokus pada lima instruksi inti, yaitu FROM, WORKDIR, RUN, COPY, dan CMD. Setiap instruksi dihubungkan dengan hasilnya pada image. Hasil itu dapat berupa layer, lapisan penyusun image yang terbentuk selama proses build atau pembangunan image, maupun konfigurasi yang tersimpan pada metadata, kumpulan informasi yang menjelaskan image. Aplikasi catatan pada contoh diuji terlebih dahulu di sandbox, lingkungan uji tanpa daemon, layanan latar yang mengeksekusi pekerjaan Docker, kemudian perintah pembangunan image dibahas pada bagian akhir.
Dockerfile utuh aplikasi catatan#
Contoh yang digunakan di sepanjang topik ini adalah aplikasi catatan pustaka standar Python, dengan app.py sebagai satu-satunya berkas sumber. Aplikasi melayani dua endpoint JSON, yaitu /healthz untuk pemeriksaan kesehatan dan /catatan untuk daftar catatan, serta membaca port dari variabel lingkungan APP_PORT dengan nilai default 8000. Image dasar yang dipilih adalah python:3.12-slim, varian slim dari image resmi Python. Varian ini berbasis Debian dengan paket bawaan yang dipangkas, sehingga ukurannya lebih kecil daripada image python:3.12 penuh, tetapi masih menyertakan pip dan shell. Dockerfile lengkapnya terlihat seperti berikut.
# Image dasar: Python 3.12 varian slim berbasis Debian
FROM python:3.12-slim
# Direktori kerja untuk instruksi berikutnya dan untuk container
WORKDIR /aplikasi
# Perintah yang dieksekusi saat build, di sini menyiapkan direktori data
RUN mkdir data
# Menyalin kode aplikasi dari build context ke dalam image
COPY app.py .
# Perintah default saat container berjalan, ditulis dalam bentuk exec
CMD ["python3", "app.py"]Hasil tiap instruksi pada image#
Setiap instruksi dikerjakan sekali, pada saat image dibangun, dan meninggalkan jejak yang berbeda pada image. Tabel berikut merangkum pekerjaan tiap instruksi beserta hasilnya.
| Instruksi | Pekerjaan saat build | Hasil pada image |
|---|---|---|
| FROM | Mengambil image dasar sebagai titik awal | Layer image dasar menjadi fondasi image baru |
| WORKDIR | Membuat direktori bila belum ada | Direktori kerja tercatat dan dipakai instruksi berikutnya |
| RUN | Mengeksekusi perintah dalam container sementara | Perubahan sistem berkas tersimpan sebagai layer baru |
| COPY | Menyalin berkas dari build context | Berkas tersimpan sebagai layer baru |
| CMD | Tidak mengeksekusi apa pun | Perintah default tercatat pada metadata image |
FROM python:3.12-slim menetapkan titik awalnya. Layer image dasar tidak diduplikasi, melainkan dibagi, sehingga image baru hanya menyimpan perbedaannya terhadap dasar tersebut. WORKDIR /aplikasi membuat direktori /aplikasi di dalam image dan mencatatnya sebagai direktori kerja, yaitu lokasi yang menjadi patokan tujuan relatif pada instruksi berikutnya, misalnya tujuan . pada COPY. Direktori yang sama menjadi direktori awal saat container berjalan.
RUN mkdir data mengeksekusi perintahnya di dalam container sementara yang dibuat dari hasil instruksi sebelumnya. Perbedaan sistem berkas pada container tersebut, yaitu direktori data yang baru, disimpan sebagai layer baru. Perintah lain seperti pip install -r requirements.txt untuk memasang dependensi ditulis dengan cara yang sama. COPY app.py . menyalin app.py dari build context, yaitu kumpulan berkas yang dikirim ke daemon saat build, ke dalam direktori kerja. Adapun CMD tidak mengerjakan apa pun saat build, karena perintahnya baru dieksekusi ketika container dibuat dari image.
Bentuk CMD ["python3", "app.py"] disebut bentuk exec, yaitu penulisan perintah sebagai array JSON yang dijalankan langsung tanpa shell. Kata python3 menjadi program yang dieksekusi, sedangkan app.py menjadi argumennya. Bentuk ini menempatkan aplikasi sebagai proses utama container, dan konsekuensinya dibahas pada topik CMD vs ENTRYPOINT.
Aplikasi catatan yang diimagekan#
Berkas app.py berikut adalah isi lengkap aplikasinya. Baris pertama berupa penanda tidak-dijalankan, karena server aplikasi berjalan terus sehingga berkas ini tidak dieksekusi sebagai blok mandiri, dan pengujian dilakukan pada blok bash di bawahnya.
# tidak-dijalankan: server berjalan terus, diuji melalui blok bash di bawah
"""Aplikasi catatan sederhana berbasis pustaka standar Python."""
import json
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
# Data catatan disimpan dalam memori
CATATAN = {
1: "Beli kopi dan gula",
2: "Tulis ringkasan rapat",
3: "Kirim laporan mingguan",
}
# Port dibaca dari variabel lingkungan APP_PORT dengan nilai default 8000
PORT = int(os.environ.get("APP_PORT", "8000"))
class LayananCatatan(BaseHTTPRequestHandler):
def _kirim_json(self, kode, data):
body = json.dumps(data, ensure_ascii=False).encode("utf-8")
self.send_response(kode)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/healthz":
self._kirim_json(200, {"status": "ok"})
elif self.path == "/catatan":
daftar = [{"id": kunci, "isi": isi} for kunci, isi in sorted(CATATAN.items())]
self._kirim_json(200, {"jumlah": len(daftar), "catatan": daftar})
else:
self._kirim_json(404, {"status": "tidak ditemukan"})
if __name__ == "__main__":
server = HTTPServer(("0.0.0.0", PORT), LayananCatatan)
print(f"Server catatan berjalan pada port {PORT}")
server.serve_forever()Pengujian dijalankan dengan menyalakan server di latar belakang, memanggil kedua endpoint dengan curl, lalu menghentikannya, semuanya dalam satu blok.
# menjalankan server catatan di latar belakang, tanpa Docker
python3 app.py >/dev/null 2>&1 &
PID=$!
# melepas proses latar dari daftar tugas shell
disown
# memberi waktu server membuka port
sleep 1
# menguji endpoint pemeriksaan kesehatan
curl -s http://localhost:8000/healthz
echo
# menguji endpoint daftar catatan
curl -s http://localhost:8000/catatan
echo
# menghentikan server setelah pengujian selesai
kill $PID{"status": "ok"}
{"jumlah": 3, "catatan": [{"id": 1, "isi": "Beli kopi dan gula"}, {"id": 2, "isi": "Tulis ringkasan rapat"}, {"id": 3, "isi": "Kirim laporan mingguan"}]}Endpoint /healthz menjawab JSON dengan status ok, sedangkan /catatan mengembalikan tiga catatan yang tersimpan pada dict, yaitu struktur data pasangan kunci-nilai di memori. Respons inilah yang diharapkan juga dari container yang dibuat dari image ini, sehingga perilaku aplikasi sudah diketahui sebelum image dibangun.
Membangun image dan menjalankan container#
Dengan Dockerfile dan app.py berada pada satu direktori, image dibangun dengan perintah berikut.
# tidak-dijalankan: daemon Docker tidak tersedia di sandbox
# membangun image bernama aplikasi-catatan dari direktori saat ini
docker build -t aplikasi-catatan .Opsi -t memberi nama aplikasi-catatan pada image hasil build, sedangkan titik di akhir menetapkan build context. Saat build berlangsung, daemon mengeksekusi instruksi Dockerfile satu per satu dari atas ke bawah, mulai dari mengambil image dasar, membuat direktori kerja, menyiapkan direktori data, menyalin app.py, hingga mencatat perintah default. Kemudian daemon mengekspor hasilnya sebagai image bernama aplikasi-catatan.
# tidak-dijalankan: daemon Docker tidak tersedia di sandbox
# menjalankan container dari image hasil build
docker run --rm -p 8000:8000 aplikasi-catatanPerintah docker run membuat container dari image dan mengeksekusi CMD yang tercatat padanya, yaitu python3 app.py. Opsi -p 8000:8000 memetakan port 8000 pada host, komputer tempat Docker berjalan, ke port 8000 pada container, sehingga kedua endpoint di atas dapat dijangkau dari http://localhost:8000/healthz dan http://localhost:8000/catatan. Opsi --rm menghapus container secara otomatis setelah container dihentikan.
Kesalahan umum#
- Mengira
CMDdieksekusi saat build.CMDhanya mencatat perintah default pada metadata image, sedangkan eksekusinya terjadi saat container dibuat. Perbaikannya, gunakanRUNuntuk pekerjaan yang harus selesai pada saat build. - Menyalin berkas sebelum menetapkan
WORKDIR. Tujuan relatif seperti.padaCOPYdihitung dari direktori kerja saat itu, sehingga tanpaWORKDIRberkas tersalin ke lokasi lain yang tidak terduga. Perbaikannya, tetapkanWORKDIRsebelum instruksiCOPY. - Mengira instruksi
RUNberjalan di sistem berkas host.RUNdieksekusi di dalam container sementara yang dibangun di atas image dasar, bukan di host. Perbaikannya, pastikan perintah dan berkas yang dirujuk tersedia di dalam image.
Ringkasan#
FROMmenetapkan image dasar yang menjadi fondasi, sedangkanWORKDIRmenetapkan direktori kerja bagi instruksi berikutnya dan bagi container.RUNdanCOPYmengerjakan perubahan saat build dan menyimpannya sebagai layer baru, sedangkanCMDhanya mencatat perintah default pada metadata image.CMDdalam bentuk exec, yaitu array JSON seperti["python3", "app.py"], menjalankan program langsung tanpa perantara shell.
Referensi#
- Dockerfile reference — Docker Docs — Rujukan lengkap seluruh instruksi Dockerfile, termasuk FROM, WORKDIR, RUN, COPY, dan CMD.
- docker image build — Docker Docs
— Dokumentasi perintah pembangunan image beserta opsi
-tdan argumen build context.