Apa itu Dockerfile? #
Dockerfile adalah salah satu file paling sederhana dalam ekosistem Docker, dan juga salah satu yang paling berpengaruh. Ia hanya berupa file teks biasa, tanpa ekstensi khusus, tanpa binary, tanpa format rahasia. Tapi dari file kecil ini lahir image yang menjadi pondasi container, fondasi pipeline CI/CD, dan kontrak antara developer dengan production. Hampir semua hal yang kamu lakukan dengan Docker bermula dari sini.
Pertanyaan yang sering muncul di awal perjalanan belajar Docker biasanya bukan “Dockerfile itu apa?” — jawabannya mudah dicari. Pertanyaan yang lebih penting adalah: mengapa file sesederhana ini bisa begitu krusial, dan konsep apa di baliknya yang harus dipahami sebelum menulis baris pertama? Tanpa pemahaman itu, Dockerfile terasa seperti deretan instruksi ajaib yang harus dihafal. Dengan pemahaman itu, Dockerfile jadi alat yang bisa kamu rancang, optimalkan, dan debug sesuai kebutuhan.
Artikel ini adalah gerbang untuk seluruh pembahasan di section Dockerfile. Setelah membacanya, kamu akan memahami apa itu Dockerfile, bagaimana posisinya dalam alur kerja Docker, instruksi apa saja yang paling sering dipakai, dan pola pikir yang membedakan Dockerfile yang ditulis asal dengan Dockerfile yang siap production.
Masalah yang Dipecahkan Dockerfile #
Untuk menghargai Dockerfile, kamu perlu melihat masalah yang ingin dipecahkannya. Masalah itu punya nama: “works on my machine” — sindrom legendaris di dunia software engineering.
Pada pendekatan tradisional, aplikasi dijalankan langsung di server. Library di-install global, versi runtime ditentukan oleh admin, dan konfigurasi tersebar di banyak tempat. Hasilnya, aplikasi yang berjalan sempurna di laptop developer sering kali gagal di server staging, lebih sering lagi gagal di production. Setiap perpindahan environment menjadi pertaruhan: apakah dependency-nya sama? Apakah versinya cocok? Apakah konfigurasinya konsisten?
Dockerfile menjawab itu dengan mendeklarasikan environment aplikasi sebagai kode. Semua yang dibutuhkan untuk menjalankan aplikasi — base OS, runtime, library, konfigurasi, dan perintah start — ditulis dalam satu file. File itu bisa di-commit ke Git, di-review, di-test, dan dijalankan di mana pun secara konsisten.
flowchart LR
A[Aplikasi Tradisional] --> B[Server: OS + Lib Global]
C[Aplikasi + Dockerfile] --> D[Image: App + Deps + Runtime]
B --> E[Konflik dependency]
D --> F[Konsisten di mana pun]
Dengan Dockerfile, istilah “works on my machine” kehilangan maknanya. Yang ada hanya satu pertanyaan: “apakah Dockerfile-nya benar?”. Jika benar, semua orang — dari laptop developer sampai kluster Kubernetes — akan menjalankan aplikasi yang sama persis.
Definisi Dockerfile #
Dockerfile adalah file teks berisi instruksi berurutan untuk membangun Docker image. Setiap baris instruksi adalah perintah yang akan dieksekusi oleh Docker daemon saat proses docker build berjalan. Hasilnya adalah image — blueprint read-only yang siap dijalankan menjadi container.
Secara konseptual, Dockerfile adalah resep untuk membuat image. Jika container adalah hidangan yang disajikan di meja, image adalah makanan yang sudah disiapkan dan dikemas, dan Dockerfile adalah buku resep yang menjelaskan bahan, urutan, dan teknik memasaknya.
Hal-hal yang dideklarasikan dalam Dockerfile:
- Base image — pondasi sistem yang akan digunakan (misal
node:20-alpine,python:3.12-slim). - Dependency — library, package, dan tool yang dibutuhkan aplikasi.
- File aplikasi — kode, konfigurasi, dan aset yang akan disalin ke image.
- Environment variable — nilai yang akan tersedia saat container berjalan.
- Perintah startup — apa yang harus dijalankan saat container di-start.
Semua informasi ini sebelumnya tersebar di banyak tempat: wiki tim, README, script install manual, dan memori kolektif developer. Dockerfile menyatukan semuanya dalam satu file yang bisa dibaca, diuji, dan diaudit.
Posisi Dockerfile dalam Alur Docker #
Dockerfile adalah titik awal dari seluruh alur kerja Docker. Ia bukan image, bukan container — ia hanya resep. Tapi dari resep ini, Docker menghasilkan dua hal berbeda.
flowchart LR
A[Dockerfile] -->|docker build| B[Docker Image]
B -->|docker run| C[Docker Container]
B -->|docker push| D[Registry]
D -->|docker pull| C
- Dockerfile — file teks berisi instruksi build. Ini yang kamu tulis dan simpan di repository.
- Docker Image — hasil build dari Dockerfile. Image bersifat read-only, immutable, dan bisa didistribusikan.
- Docker Container — instance runtime dari image. Container adalah proses yang berjalan, dengan state yang bisa berubah.
Yang perlu kamu garis bawahi: Dockerfile tidak pernah dieksekusi langsung oleh container. Container berjalan dari image, dan image dibangun dari Dockerfile. Dockerfile selalu satu langkah di belakang.
Perintah yang relevan dengan masing-masing tahap:
# Membangun image dari Dockerfile
docker build -t myapp:1.0 .
# Menjalankan image menjadi container
docker run -d -p 8080:80 myapp:1.0
# Mendistribusikan image ke registry
docker push myapp:1.0
Perintah docker build membaca Dockerfile dari baris pertama sampai terakhir, mengeksekusi setiap instruksi, dan menyusun image berlapis-lapis (layered). Perintah docker run tidak pernah melihat isi Dockerfile — yang dilihatnya hanya image final.
Anatomi Dockerfile: Contoh Pertama #
Untuk melihat seperti apa Dockerfile dalam bentuk paling sederhana, perhatikan contoh berikut.
# Baris komentar diabaikan oleh Docker
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Setiap baris punya peran:
FROM— menentukan base image. Ini wajib ada dan harus menjadi instruksi pertama (kecuali adaARGkhusus sebelumFROM).WORKDIR— mengatur direktori kerja di dalam image. Setelah baris ini, semua path relatif akan berdasarkan direktori tersebut.COPY— menyalin file dari host (build context) ke dalam image.RUN— mengeksekusi perintah saat build. Di sini,npm installdijalankan untuk meng-install dependency.EXPOSE— mendokumentasikan port yang akan digunakan aplikasi. Hanya dokumentasi, tidak membuka port.CMD— menentukan perintah default saat container dijalankan. Hanya boleh ada satuCMD(yang terakhir menimpa yang sebelumnya).
Urutan baris-baris ini bukan kebetulan. package.json dan package-lock.json di-copy dan di-install lebih dulu sebelum seluruh source code disalin. Ini disengaja untuk memanfaatkan layer cache Docker — topik yang akan dibahas mendalam nanti, tapi intinya: instruksi yang jarang berubah diletakkan di atas, yang sering berubah di bawah.
// ANTI-PATTERN: COPY seluruh kode di awal, cache npm install selalu invalid
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
// BENAR: COPY dependency dulu, npm install bisa di-cache antar build
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
Dengan pola kedua, setiap kali kamu mengubah kode aplikasi, Docker tidak perlu menjalankan npm install lagi — layer npm install di-reuse dari cache. Build menjadi jauh lebih cepat.
Filosofi Infrastructure as Code #
Dockerfile bukan sekadar konfigurasi — ia adalah kode. Artinya, ia tunduk pada prinsip-prinsip software engineering: version control, code review, testing, dan reproducibility.
Version control. Dockerfile disimpan bersama kode aplikasi di Git. Setiap perubahan tercatat, setiap diff bisa di-review, dan setiap versi bisa di-revert.
Code review. Pull request untuk Dockerfile sama pentingnya dengan pull request untuk kode aplikasi. Image yang buruk di-deploy ke production bisa menjadi masalah keamanan, stabilitas, dan biaya.
Testing. Dockerfile bisa di-test. Tools seperti hadolint memvalidasi Dockerfile terhadap praktik terbaik. CI/CD pipeline bisa memastikan image yang dibuild lolos scan vulnerability sebelum di-push.
Reproducibility. Dockerfile yang sama, dengan konteks yang sama, akan menghasilkan image yang sama. Tidak ada “tapi di server saya beda” lagi.
flowchart TD
A[Code + Dockerfile di Git] --> B[CI/CD Pipeline]
B --> C[docker build]
C --> D{Hasil valid?}
D -- Ya --> E[docker push ke Registry]
D -- Tidak --> F[Gagal: notifikasi developer]
E --> G[Deploy ke Staging]
G --> H{Staging OK?}
H -- Ya --> I[Deploy ke Production]
H -- Tidak --> F
Filosofi ini berbeda dengan pendekatan tradisional di mana setup environment adalah tanggung jawab admin server, sering tidak terdokumentasi, dan berubah seiring waktu. Dengan Dockerfile, environment aplikasi terdefinisi, deterministik, dan dapat diaudit.
Peran Dockerfile dalam Pipeline Modern #
Dockerfile adalah kontrak yang menghubungkan lima pihak: developer, CI/CD, registry, orchestrator, dan production environment. Setiap pihak berinteraksi dengan hasil Dockerfile, bukan dengan Dockerfile itu sendiri.
Developer menulis dan memelihara Dockerfile. Tujuannya: menghasilkan image yang representatif untuk aplikasi.
CI/CD mengeksekusi docker build setiap kali ada perubahan kode. Tujuannya: menghasilkan image baru yang bisa diuji.
Registry menyimpan image yang sudah jadi. Tujuannya: menjadi single source of truth untuk image yang akan di-deploy.
Orchestrator (Kubernetes, ECS, Nomad) menarik image dari registry dan menjalankannya. Tujuannya: menyediakan platform runtime untuk container.
Production adalah tempat image benar-benar berjalan melayani user. Tujuannya: stabilitas, performa, dan keandalan.
Karena kelima pihak ini berkomunikasi lewat image, kualitas Dockerfile menentukan kualitas seluruh pipeline. Dockerfile yang buruk = image yang buruk = deploy yang tidak konsisten = incident yang bisa dihindari.
Dockerfile vs Docker Compose: Jangan Tertukar #
Salah satu kebingungan paling umum di awal perjalanan Docker adalah mencampuradukkan Dockerfile dengan Docker Compose. Keduanya sama-sama pakai format YAML-like, dan keduanya sering muncul bersama. Tapi perannya berbeda total.
| Aspek | Dockerfile | Docker Compose |
|---|---|---|
| Tujuan | Membangun image | Menjalankan banyak container |
| Fokus | Satu aplikasi | Arsitektur multi-service |
| Format | Instruksi imperatif | Deklaratif (YAML) |
| Perintah | docker build |
docker compose up |
| Output | Image | Container yang berjalan |
| Dipakai di | Build time | Runtime |
Dockerfile menjawab pertanyaan: “Bagaimana cara membuat image untuk aplikasi ini?” Docker Compose menjawab pertanyaan: “Aplikasi ini butuh database, cache, dan message broker — bagaimana cara menjalankan semuanya sekaligus?”
# docker-compose.yml — bukan Dockerfile
services:
app:
build: .
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
Pada contoh di atas, build: . memberitahu Docker Compose untuk membaca Dockerfile dari direktori saat ini. Tapi setelah image dibuild, peran Compose adalah mengorkestrasi container, bukan membangun image.
Dockerfile wajib untuk image custom. Docker Compose opsional — kamu bisa menjalankan multi-container tanpa Compose, tapi tidak akan senyaman dan se-ergonomis dengan Compose. Untuk local development, Docker Compose hampir selalu menjadi pilihan utama.
Konsep Layer dan Cache #
Salah satu konsep paling penting di Dockerfile adalah layer. Setiap instruksi di Dockerfile menghasilkan satu layer di image. Layer-layer ini di-cache dan di-reuse di build berikutnya. Memahami layer berarti memahami mengapa urutan instruksi menentukan kecepatan build.
flowchart TB
subgraph Image["Docker Image (read-only)"]
L1[Layer 1: Base OS - FROM]
L2[Layer 2: WORKDIR]
L3[Layer 3: COPY package.json]
L4[Layer 4: RUN npm install]
L5[Layer 5: COPY source code]
end
L1 --> L2 --> L3 --> L4 --> L5
Docker membandingkan layer baru dengan cache dari build sebelumnya. Jika layer identik, Docker me-reuse cache. Jika tidak, Docker membangun ulang layer itu dan semua layer setelahnya.
Implikasinya sangat konkret:
- Jika
package.jsontidak berubah, layernpm installdi-reuse — build cepat. - Jika
package.jsonberubah, layernpm installdi-rebuild — dan semua layer setelahnya (termasukCOPY source code) ikut di-rebuild.
Makanya, instruksi yang sering berubah diletakkan di bawah, dan instruksi yang jarang berubah di atas. Prinsip ini akan dibahas mendalam di artikel best practice, tapi untuk sekarang, cukuplah diingat: urutan Dockerfile bukan soal estetika, tapi soal performa build.
// ✗ Anti-pattern: source code di atas, cache sering invalid
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
// ✓ Benar: dependency dulu, source code belakangan
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
Kesalahan Umum Saat Menulis Dockerfile #
Dockerfile terlihat sederhana, dan memang sederhana — di permukaan. Tapi ada beberapa jebakan yang sering menjerat pemula (bahkan yang sudah berpengalaman). Mengenali jebakan ini sejak awal akan menghemat banyak waktu.
1. Base image terlalu besar. Memilih ubuntu:latest atau node:latest untuk produksi membuat image membengkak dan attack surface melebar. Selalu mulai dari base image yang paling sesuai dengan kebutuhan.
2. Tidak memanfaatkan layer cache. Menyalin seluruh kode di awal, lalu menjalankan npm install, membuat cache tidak pernah berguna. Urutan instruksi adalah segalanya.
3. Menyalin file sensitif tanpa .dockerignore. File .env, .git, folder node_modules lokal, dan kredensial bisa ikut masuk build context. Tanpa .dockerignore, kamu mengirim lebih banyak data ke daemon dan berpotensi membocorkan rahasia.
4. Menjalankan aplikasi sebagai root. Default container berjalan sebagai root. Ini berbahaya — jika container dieksploitasi, attacker mendapat akses root di host. Selalu buat user non-root.
5. Menggabungkan terlalu banyak RUN tanpa cleanup. Menginstal paket tanpa menghapus cache di layer yang sama membuat layer membengkak. Cache apt, apk, dan pip harus dihapus dalam RUN yang sama dengan instalasi.
Dockerfile yang buruk = build lambat + image besar + risiko keamanan. Untungnya, semua jebakan ini bisa dihindari dengan disiplin dan pola pikir yang benar.
Kapan Kamu Membutuhkan Dockerfile? #
Dockerfile wajib ketika kamu ingin image yang representatif untuk aplikasimu. Tanpa Dockerfile, kamu hanya bisa menggunakan image publik dari Docker Hub — yang jarang sekali cocok dengan kebutuhan spesifik aplikasimu.
Dockerfile sangat cocok ketika:
- Aplikasi akan di-deploy ke environment apapun (cloud, on-premise, Kubernetes, serverless).
- Kamu menggunakan CI/CD dan ingin build image otomatis dari kode.
- Tim kamu terdiri dari banyak orang dengan environment lokal yang berbeda-beda.
- Aplikasi adalah microservice dengan satu proses utama per service.
- Kamu butuh reproducibility — image yang sama harus bisa dibangun kapan saja, di mana saja.
Dockerfile juga sangat membantu bahkan untuk eksperimen lokal. Tidak perlu mulai dari Kubernetes untuk mendapat manfaat Dockerfile. Cukup punya Dockerfile yang baik, dan aplikasimu siap dipindah ke environment manapun tanpa kejutan.
Ringkasan #
- Dockerfile adalah file teks berisi instruksi berurutan untuk membangun Docker image. Ia adalah resep, bukan hidangan — bukan image, bukan container.
- Posisi Dockerfile ada di awal alur:
Dockerfile → Image → Container. Dockerfile diproses olehdocker build, bukan dijalankan langsung.- Anatomi dasar Dockerfile:
FROM(base image),WORKDIR(direktori kerja),COPY/ADD(salin file),RUN(eksekusi perintah saat build),CMD/ENTRYPOINT(perintah saat container jalan).- Filosofi utama Dockerfile adalah infrastructure as code: konfigurasi environment ditulis, di-review, dan di-version control seperti kode aplikasi.
- Layer dan cache adalah konsep fundamental. Urutan instruksi menentukan apakah build berikutnya bisa reuse cache atau harus rebuild dari awal.
- Dockerfile ≠ Docker Compose. Dockerfile membangun image untuk satu aplikasi. Docker Compose menjalankan banyak container bersama.
- Kesalahan umum yang harus dihindari: base image terlalu besar, urutan instruksi salah, tidak ada
.dockerignore, root user, dan lupa cleanup cache.- Kapan butuh Dockerfile — hampir selalu, selama kamu ingin image yang representatif dan portabel untuk aplikasimu.
← Sebelumnya: User Defined Network Berikutnya: Struktur Dockerfile →