Mengurangi Ukuran Image Kontainer dengan Docker Multi-Stage Build
(labs.iximiuz.com)- Saat membangun image kontainer Docker, jika Dockerfile tidak menggunakan struktur Multi-Stage, kemungkinan besar file yang tidak perlu akan ikut disertakan
- Hal ini menyebabkan ukuran image membesar dan meningkatkan kerentanan keamanan
- Analisis penyebab utama “file yang tidak perlu” yang dapat muncul dalam image kontainer, serta penjelasan cara mengatasinya melalui Multi-Stage Build
Penyebab ukuran image membesar
- Aplikasi memiliki dependensi build-time dan runtime.
- Dependensi build-time lebih banyak daripada runtime dan memiliki lebih banyak kerentanan keamanan (CVE).
- Jika image yang sama digunakan untuk build dan menjalankan aplikasi, dependensi build-time yang tidak perlu (kompiler, linter, dan lain-lain) akan ikut masuk.
- Image build dan runtime seharusnya dipisahkan, tetapi hal ini sering terlewatkan.
Contoh struktur Dockerfile yang salah
Contoh yang salah untuk aplikasi Go
FROM golang:1.23
WORKDIR /app
COPY . .
RUN go build -o binary
CMD ["/app/binary"]
- Image golang:1.23 ditujukan untuk kompilasi, tetapi jika dipakai apa adanya di lingkungan produksi, seluruh compiler Go beserta dependensinya akan ikut disertakan.
- Ukuran image: lebih dari 800MB, dengan lebih dari 800 kerentanan keamanan.
Contoh yang salah untuk aplikasi Node.js
FROM node:lts-slim
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "/app/.output/index.mjs"]
- Folder node_modules akan ikut memuat dependensi pengembangan yang tidak dibutuhkan saat runtime.
- Ini tidak bisa diperbaiki hanya dengan
npm ci --omit=dev, karena dependensi pengembangan yang diperlukan dalam proses build bisa ikut terhapus.
Cara membuat image lean sebelum ada Multi-Stage Build
Pola Builder
- Build aplikasi di Dockerfile.build:
FROM node:lts-slim
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
- Salin artefak hasil build ke host:
docker cp $(docker create build:v1):/app/.output .
- Buat image runtime di Dockerfile.run:
FROM node:lts-slim
WORKDIR /app
COPY .output .
CMD ["node", "/app/.output/index.mjs"]
• Masalah: perlu menulis beberapa Dockerfile, mengelola urutan build, dan membutuhkan skrip tambahan.
Memahami Multi-Stage Build
- Multi-Stage Build adalah fitur yang mengimplementasikan pola Builder di dalam Docker.
- Dengan beberapa perintah FROM, tahap build dan runtime dapat didefinisikan dalam satu Dockerfile.
- Gunakan perintah COPY --from=<stage> untuk mengambil file yang dibangun dari stage sebelumnya.
Contoh Dockerfile Multi-Stage (Node.js)
# Build stage
FROM node:lts-slim AS build
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
# Runtime stage
FROM node:lts-slim AS runtime
WORKDIR /app
COPY --from=build /app/.output .
ENV NODE_ENV=production
CMD ["node", "/app/.output/index.mjs"]
- Dengan COPY --from=build, artefak hasil build dapat disalin langsung tanpa melewati host.
Contoh penerapan Multi-Stage Build
Aplikasi React
# Build stage
FROM node:lts-slim AS build
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
# Runtime stage
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
ENTRYPOINT ["nginx", "-g", "daemon off;"]
- Setelah dibuild, aplikasi React menjadi file statis dan dapat disajikan dengan Nginx.
Aplikasi Go
# Build stage
FROM golang:1.23 AS build
WORKDIR /app
COPY . .
RUN go build -o binary
# Runtime stage
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /app/binary /app/binary
ENTRYPOINT ["/app/binary"]
- Menggunakan image distroless untuk menyediakan lingkungan runtime yang lebih minimal.
Aplikasi Java
# Build stage
FROM eclipse-temurin:21-jdk-jammy AS build
WORKDIR /build
COPY . .
RUN ./mvnw package -DskipTests
# Runtime stage
FROM eclipse-temurin:21-jre-jammy
COPY --from=build /build/target/app.jar /app.jar
CMD ["java", "-jar", "/app.jar"]
- Gunakan JDK untuk build, lalu JRE yang lebih ringan untuk runtime.
Kesimpulan
- Multi-Stage Build memisahkan lingkungan build dan runtime untuk mencegah ukuran image membesar akibat dependensi pengembangan yang tidak perlu
- Dengan ini, ukuran image dapat dikurangi, keamanan ditingkatkan, dan proses build disederhanakan
- Multi-Stage Build adalah metode standar untuk membuat image kontainer yang efisien, dan juga mendukung fitur lanjutan (misalnya kondisi percabangan, unit test saat build)
6 komentar
Untuk Java,
jlinkmemang diperkenalkan sejak versi 9, tetapi kegunaannya kurang baik karena kita harus mencari dan menyebutkan modul dependensi denganjdeps, dan sebagainya. Melihat banyak orang tidak tahu metode seperti itu atau masih mencari JRE, rasanya promosi untuk alat Java masih kurang, dan tampaknya perlu ada perbaikan agar JRE bisa dihasilkan hanya dengan satu perintah.Saya memang menggunakannya seperti itu, tetapi kekurangannya sepertinya waktu build jadi lama
Waktu build seharusnya tidak berbeda. Kalau ada perbedaan, berarti konfigurasinya salah!
Ah, begitu ya!
Tergantung strateginya, satu stage bahkan bisa di-cache secara utuh, jadi menurut saya justru waktu build-nya jadi lebih singkat!
Sepertinya saya perlu belajar lebih banyak tentang Docker!