Docker Multi-Stage Build: Panduan Praktis Optimasi Image untuk CI/CD
AW
Axel W

Dipublikasikan 12 Juni 2026

Docker Multi-Stage Build: Panduan Praktis Optimasi Image untuk CI/CD

Docker Multi-Stage Build adalah teknik yang memungkinkan developer untuk memisahkan tahap build dan runtime dalam satu Dockerfile. Hasilnya adalah image produksi yang lebih kecil, lebih aman, dan lebih cepat di-deploy. Artikel ini akan membahas panduan praktis menggunakan Docker Multi-Stage Build untuk optimasi CI/CD pipeline, dengan contoh nyata untuk aplikasi Node.js dan Go.

Mengapa Multi-Stage Build Penting untuk Pipeline Modern

Di banyak pipeline CI/CD, image Docker yang dihasilkan sering kali membawa beban yang tidak perlu: compiler, dev dependencies, build tools, dan file sementara. Semua ini meningkatkan ukuran image, memperpanjang waktu pull, dan memperluas attack surface. Multi-Stage Build menyelesaikan masalah ini dengan membangun aplikasi di satu stage dan hanya menyalin artifact yang diperlukan ke stage runtime.

Dengan pendekatan ini, image produksi bisa berkurang dari ratusan megabyte menjadi puluhan megabyte. Untuk aplikasi Go, misalnya, image final bisa turun dari 800MB menjadi 15MB. Penghematan ini berdampak langsung pada kecepatan deployment dan biaya storage registry. Di environment dengan ratusan service microservices, penghematan ini menjadi signifikan secara kumulatif.

Langkah 1: Memahami Struktur Dockerfile Multi-Stage

Dockerfile multi-stage menggunakan multiple FROM statements. Setiap FROM memulai stage baru dan bisa menggunakan base image yang berbeda. Sintaks dasar terlihat seperti ini:

FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/main.js"]

Pada contoh di atas, stage builder menangani kompilasi, sedangkan stage runtime hanya menyalin hasil build dan dependency produksi. Image final tidak menyertakan source code TypeScript, compiler, atau dev tools. Proses ini terjadi dalam satu build invocation, sehingga tidak ada overhead tambahan dari sisi developer.

Langkah 2: Optimasi Stage Build untuk Kecepatan Maksimal

Salah satu best practice adalah menyusun layer Docker dengan urutan yang tepat. Letakkan perintah yang paling jarang berubah di paling atas. Untuk aplikasi Node.js, salin package.json dan package-lock.json terlebih dahulu, baru jalankan npm ci. Ini memanfaatkan Docker layer caching dan mempercepat rebuild.

Jika project menggunakan monorepo dengan tool seperti TurboRepo atau Nx, pastikan untuk menyalin hanya file yang relevan. Gunakan .dockerignore untuk mengecualikan test file, dokumentasi, dan direktori yang tidak diperlukan saat build. Contoh .dockerignore:

node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.env
tests
coverage

File .dockerignore yang lengkap bisa mengurangi context size dari ratusan megabyte menjadi beberapa megabyte saja. Ini mempercepat proses COPY dan mengurangi beban I/O di build server.

Langkah 3: Memilih Base Image yang Tepat untuk Runtime

Pemilihan base image runtime sangat menentukan ukuran dan keamanan image. Prioritaskan image yang minimal seperti alpine, distroless, atau scratch (untuk bahasa compiled seperti Go dan Rust). Untuk Node.js, node:20-alpine adalah pilihan yang seimbang antara ukuran dan kompatibilitas.

Untuk aplikasi Go, gunakan image gcr.io/distroless/static atau bahkan scratch jika aplikasi tidak memerlukan SSL certificate atau timezone data. Berikut contoh Dockerfile Go multi-stage:

FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM scratch
WORKDIR /app
COPY --from=builder /app/app .
EXPOSE 8080
ENTRYPOINT ["./app"]

Langkah 4: Mengurangi Attack Surface dengan Distroless

Image distroless dari Google Cloud tidak mengandung shell, package manager, atau utilitas sistem. Ini secara drastis mengurangi attack surface karena attacker tidak bisa mengeksekusi shell meskipun berhasil mengeksploitasi aplikasi. Distroless tersedia untuk Java, Python, Node.js, .NET, dan Go.

Untuk menggunakan distroless dengan Node.js, ganti stage runtime menjadi:

FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/node_modules /app/node_modules
WORKDIR /app
CMD ["dist/main.js"]

Perlu diperhatikan bahwa distroless tidak memiliki shell, sehingga debugging dengan docker exec tidak memungkinkan. Untuk troubleshooting, gunakan ephemeral debug container atau sidecar dengan image lengkap.

Langkah 5: Integrasi dengan CI/CD Pipeline

Integrasi Multi-Stage Build dengan CI/CD pipeline menghasilkan workflow yang lebih bersih. Gunakan Docker Buildx untuk build cross-platform dan cache yang lebih efisien. Contoh di GitHub Actions:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: user/app:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

Fitur cache-from dan cache-to menggunakan GitHub Actions cache untuk menyimpan layer Docker antar build. Ini mengurangi waktu build secara signifikan untuk project yang sering di-update.

Langkah 6: Monitoring dan Validasi Image Size

Setelah mengimplementasikan Multi-Stage Build, pantau ukuran image secara berkala. Gunakan perintah docker history untuk melihat layer mana yang paling besar. Tool seperti dive bisa memberikan analisis visual yang lebih detail:

docker run --rm -it   -v /var/run/docker.sock:/var/run/docker.sock   wagoodman/dive:latest user/app:latest

Dengan dive, developer bisa melihat efisiensi setiap layer, menemukan file yang tidak perlu, dan mengoptimasi Dockerfile lebih lanjut. Target efisiensi ideal adalah di atas 90%.

Kesimpulan

Docker Multi-Stage Build adalah teknik fundamental yang harus dikuasai setiap engineer yang bekerja dengan container. Dengan memisahkan build dan runtime, image menjadi lebih kecil, lebih aman, dan lebih cepat di-deploy. Kombinasi dengan base image minimal seperti Alpine atau Distroless, serta caching layer yang tepat, akan menghasilkan pipeline CI/CD yang optimal.

Untuk informasi lebih lanjut, kunjungi dokumentasi resmi Docker Multi-Stage Build atau eksplorasi repository Distroless di GitHub.