Dockerfile adalah file teks berisi resep untuk membuat image: satu instruksi per baris, dijalankan dari atas ke bawah oleh docker build. Tiap instruksi menghasilkan satu layer (lapisan) di image. Bayangin juru masak yang menyimpan hasil tiap langkah resep di kulkas. Besok dia masak resep yang sama: selama langkahnya masih sama persis, dia ambil hasilnya dari kulkas. Begitu ketemu langkah pertama yang berubah, dia masak ulang dari langkah itu sampai habis. Kulkas itu build cache-nya Docker.
🧪 Tebakan cepat: Dockerfile-mu menyalin semua file (
COPY . .) lalu menjalankanRUN npm ci. Kamu cuma mengubah satu baris diapp.js, lalu build ulang. Apakahnpm ci(15 detik) jalan lagi? Tebak dulu, jawabannya ada di bagian "Urutan baris menentukan cache".
Dockerfile untuk catatan-api
Ini resep image catatan-api yang kita jalankan di lesson sebelumnya. Simpan di folder project dengan nama Dockerfile (tanpa ekstensi):
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
FROM node:20-alpine: base image, titik awal image-mu. Di sini Alpine Linux yang sudah berisi Node 20. Selalu baris pertama.WORKDIR /app: folder kerja di dalam image. Perintah berikutnya jalan di sini, dan foldernya dibuat kalau belum ada.COPY package*.json ./: salin file dari laptop (lebih tepatnya dari build context, folder yang dikirim ke Docker) ke dalam image.package*.jsoncocok denganpackage.jsondanpackage-lock.json.RUN npm ci --omit=dev: jalankan perintah saat build, dan hasilnya (foldernode_modules) jadi layer baru.--omit=devmelewatkan devDependencies.COPY . .: salin sisa project, termasukapp.js.EXPOSE 3000: catatan bahwa app mendengarkan port 3000. Cuma dokumentasi: port-nya tetap harus dibuka dengan-psaatdocker run.CMD ["node", "app.js"]: perintah yang dijalankan setiap container nyala. Ditulis sebagai larik dengan tanda kutip ganda. Cuma satuCMDyang berlaku; kalau ada dua, yang terakhir menang.
Instruksi lain yang sering muncul: ENV NODE_ENV=production mengisi environment variable untuk app.
Build: dari Dockerfile jadi image
docker build -t catatan-api:1.1 .
-t catatan-api:1.1 memberi nama dan tag ke image-nya. Titik di akhir adalah build context: folder ini dikirim ke Docker, dan hanya file di dalamnya yang bisa di-COPY. Build pertama menjalankan semua langkah. Potongan output-nya:
=> [1/5] FROM docker.io/library/node:20-alpine@sha256:2d07db07a2df6830718a 0.0s
=> [2/5] WORKDIR /app 0.1s
=> [3/5] COPY package*.json ./ 0.0s
=> [4/5] RUN npm ci --omit=dev 14.8s
=> [5/5] COPY . . 0.1s
[4/5] artinya langkah keempat dari lima. Baris FROM terpotong mengikuti lebar terminal; aslinya diikuti kode sha256: panjang yang menandai versi persis base image-nya (dibahas di lesson Container Registry). EXPOSE dan CMD gak dihitung: mereka cuma mencatat pengaturan, gak membuat file.
Urutan baris menentukan cache
Docker menyimpan hasil tiap langkah. Untuk COPY, cache-nya cuma dipakai kalau isi file yang disalin sama persis. Begitu satu langkah dijalankan ulang, semua langkah di bawahnya ikut dijalankan ulang. Bandingkan dua urutan setelah kamu cuma mengubah app.js:
| Langkah | Lambat: COPY . . sebelum npm ci |
Cepat: COPY . . sesudah npm ci |
|---|---|---|
COPY package*.json ./ |
CACHED | CACHED |
COPY . . |
jalan (app.js berubah) | (urutannya di bawah) |
RUN npm ci --omit=dev |
jalan lagi, 14,8 detik | CACHED |
COPY . . |
(sudah di atas) | jalan, 0,1 detik |
| Total build | sekitar 17 detik | sekitar 1,5 detik |
Jadi ya, di urutan lambat npm ci jalan lagi, walau package.json gak berubah, karena langkah di atasnya (COPY . .) sudah berubah. Itu jawaban tebakannya. Di urutan cepat, output build-nya begini:
=> CACHED [2/5] WORKDIR /app 0.0s
=> CACHED [3/5] COPY package*.json ./ 0.0s
=> CACHED [4/5] RUN npm ci --omit=dev 0.0s
=> [5/5] COPY . . 0.1s
Aturannya: yang jarang berubah (base image, daftar dependency) di atas, yang sering berubah (kode) di bawah. Kalau kamu menambah dependency, package.json berubah, dan npm ci memang harus jalan lagi.
.dockerignore
COPY . . menyalin semua isi build context, termasuk yang gak boleh masuk. Buat file .dockerignore di sebelah Dockerfile:
node_modules
.git
.env
*.log
node_modulesdari laptop gak ikut, jadi gak menimpa hasilnpm cidi image (bisa beda OS)..envberisi secret; secret yang masuk image bisa dibaca siapa pun yang memegang image-nya.- Build context jadi kecil, build lebih cepat.
Pengamanan image yang lebih lengkap (user non-root, secret saat build) ada di lesson Docker Security Basics di track Security.
💡 Multi-stage build: image lebih kecil
Kalau app-mu punya langkah build (misalnya TypeScript dikompilasi jadi dist/), tool build-nya cuma dibutuhkan saat build. Multi-stage build memakai dua FROM: tahap pertama membangun, tahap kedua cuma mengambil hasilnya.
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/app.js"]
COPY --from=builder menyalin dari tahap builder, bukan dari laptop. Yang sampai di image akhir cuma dist/ dan dependency production. Contoh ukuran: satu tahap di atas node:20 sekitar 1,35 GB, multi-stage di atas node:20-alpine sekitar 182 MB.
⚠️ Jebakan yang sering ketemu
COPY . .sebelumRUN npm ci. Tiap edit kode, semua dependency di-install ulang. Salinpackage*.jsondulu,npm ci, baruCOPY . ..- Lupa
.dockerignore.node_modulesdan.envdari laptop ikut masuk image. - Mengira
EXPOSEmembuka port. Dia cuma catatan. Tanpa-p 3000:3000saatdocker run, app gak bisa dibuka dari luar container. - Tertukar
RUNdanCMD.RUNjalan sekali saat build (install, compile).CMDjalan setiap container nyala.CMDditulis sebagai larik dengan tanda kutip ganda:["node", "app.js"]; tanda kutip tunggal bukan larik yang sah, jadi Docker menjalankannya lewat shell.
TL;DR: Dockerfile itu resep image, satu instruksi per baris, dan tiap instruksi jadi layer. FROM memilih base image, WORKDIR folder kerja, COPY menyalin file dari build context, RUN menjalankan perintah saat build, EXPOSE mencatat port, CMD ["node", "app.js"] perintah saat container nyala. Build dengan docker build -t catatan-api:1.1 .. Docker memakai cache per langkah, dan begitu satu langkah berubah, semua di bawahnya dijalankan ulang, jadi urutannya: COPY package*.json ./, RUN npm ci, baru COPY . .. .dockerignore mencegah node_modules dan .env masuk image, dan multi-stage build membuang tool build dari image akhir.