Dockerfile & Images — DevOps

Membuat Docker Image dengan Dockerfile Dockerfile adalah file teks berisi instruksi step-by-step untuk membuat Docker image. Setiap instruksi membuat layer…

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 menjalankan RUN npm ci. Kamu cuma mengubah satu baris di app.js, lalu build ulang. Apakah npm 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"]

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

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

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.

Yang akan kamu pelajari