Kalau di artikel sebelumnya kita sudah paham konsep Docker dan container, sekarang saatnya masuk ke praktik. Dockerfile adalah file teks berisi serangkaian instruksi yang digunakan Docker untuk membangun sebuah Image. Memahami cara menulis Dockerfile yang baik adalah keterampilan inti yang harus dimiliki setiap developer yang bekerja dengan Docker.
Anatomi Dockerfile
Setiap Dockerfile terdiri dari instruksi-instruksi yang dieksekusi secara berurutan dari atas ke bawah. Setiap instruksi menghasilkan satu layer di dalam Image.
# Komentar diawali dengan tanda pagar
INSTRUKSI argumen
Instruksi-Instruksi Utama Dockerfile
FROM — Base Image
Instruksi pertama dan wajib. Mendefinisikan image dasar yang digunakan.
FROM node:20-alpine # Node.js 20 di atas Alpine Linux (ringan)
FROM ubuntu:22.04 # Ubuntu 22.04
FROM python:3.12-slim # Python 3.12 versi slim
FROM nginx:latest # Nginx terbaru
Gunakan tag spesifik (node:20-alpine) daripada latest untuk build yang reproducible.
WORKDIR — Set Working Directory
Menetapkan direktori kerja di dalam container untuk instruksi selanjutnya.
WORKDIR /app
COPY — Copy File ke Image
Menyalin file dari host ke filesystem Image.
COPY package.json ./ # copy package.json ke /app/
COPY . . # copy semua file ke /app/
COPY src/ ./src/ # copy folder src/
ADD — Seperti COPY tapi Lebih Powerful
Mirip COPY tapi bisa otomatis mengekstrak file .tar dan mengambil file dari URL. Gunakan COPY untuk kasus biasa, ADD hanya jika benar-benar butuh fitur ekstranya.
RUN — Eksekusi Perintah Saat Build
Menjalankan perintah shell dan hasilnya menjadi layer baru di Image.
RUN npm install
RUN apt-get update && apt-get install -y curl
RUN npm run build
Gabungkan perintah dengan && untuk mengurangi jumlah layer:
# Kurang baik — 3 layer
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# Lebih baik — 1 layer
RUN apt-get update && apt-get install -y curl && apt-get clean
ENV — Environment Variable
Menetapkan environment variable yang tersedia saat build dan runtime.
ENV NODE_ENV=production
ENV PORT=3000
ENV APP_NAME="Blog API"
EXPOSE — Dokumentasi Port
Mendokumentasikan port yang digunakan container. Tidak otomatis membuka port — hanya metadata.
EXPOSE 3000
EXPOSE 80 443
CMD — Perintah Default Saat Container Dijalankan
Perintah yang dijalankan saat container start. Bisa di-override dengan docker run.
CMD ["node", "index.js"]
CMD ["npm", "start"]
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT — Perintah Utama Container
Mirip CMD tapi lebih rigid — tidak mudah di-override. Cocok untuk container yang berperan sebagai executable.
ENTRYPOINT ["node"]
CMD ["index.js"] # argumen default untuk ENTRYPOINT
Dockerfile untuk Aplikasi Node.js
# Gunakan Node.js 20 Alpine (lebih ringan dari full image)
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package files dulu (manfaatkan layer cache)
COPY package.json package-lock.json ./
# Install dependencies
RUN npm ci --omit=dev
# Copy source code
COPY . .
# Set environment variable
ENV NODE_ENV=production
ENV PORT=3000
# Dokumentasikan port
EXPOSE 3000
# Jalankan aplikasi
CMD ["node", "index.js"]
Build dan Jalankan Image
# Build image dari Dockerfile di direktori saat ini
# -t = tag (nama:versi)
docker build -t blog-api:1.0 .
# Lihat image yang sudah di-build
docker images
# Jalankan container dari image
docker run -p 3000:3000 blog-api:1.0
# Jalankan di background (detached mode)
docker run -d -p 3000:3000 --name api blog-api:1.0
# Lihat container yang berjalan
docker ps
# Lihat log container
docker logs api
docker logs -f api # follow (live log)
# Stop dan hapus container
docker stop api
docker rm api
Flag -p 3000:3000 artinya: port 3000 di host → port 3000 di container.
.dockerignore — Jangan Copy File yang Tidak Perlu
Seperti .gitignore, file .dockerignore mencegah file tertentu ter-copy ke Image:
node_modules
.git
.env
*.log
dist
coverage
README.md
docker-compose.yml
.dockerignore
Tanpa .dockerignore, folder node_modules yang berisi ribuan file akan ter-copy ke Image dan memperlambat build secara signifikan.
Layer Caching — Optimalkan Kecepatan Build
Docker menyimpan cache setiap layer. Jika sebuah layer tidak berubah, Docker menggunakan cache dan tidak rebuild layer tersebut.
Trik penting: letakkan instruksi yang jarang berubah di atas, yang sering berubah di bawah.
FROM node:20-alpine
WORKDIR /app
# COPY package.json dulu — hanya berubah saat ada dependency baru
COPY package.json package-lock.json ./
RUN npm ci
# COPY source code SETELAH install — source code sering berubah
COPY . .
CMD ["node", "index.js"]
Dengan urutan ini, npm ci hanya dijalankan ulang saat package.json berubah, bukan setiap kali kode berubah. Build menjadi jauh lebih cepat.
Multi-Stage Build — Image yang Lebih Kecil
Multi-stage build memungkinkan kamu menggunakan beberapa FROM untuk memisahkan proses build dari runtime image:
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production image (hanya copy hasil build)
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
Hasilnya: image production tidak mengandung devDependencies maupun source code mentah — ukurannya jauh lebih kecil.
Kesimpulan
Dockerfile adalah fondasi dari semua workflow Docker. Dengan memahami instruksi-instruksi utama, manfaat layer caching, dan multi-stage build, kamu bisa membuat Image yang efisien, kecil, dan reproducible.
Di artikel berikutnya, kita bahas Docker Compose — cara mengelola aplikasi yang terdiri dari beberapa container sekaligus.
Kiki/🎮🍉⌨️🍩💻