Ukuran image Docker adalah metrik yang sering diabaikan oleh para pengembang. Image yang membengkak tidak hanya memakan ruang penyimpanan yang besar, tetapi juga memperlambat proses build, distribusi, dan deployment. Bayangkan harus mengunduh image berukuran 500MB ke setiap server setiap kali ada pembaruan kode—itu sangat tidak efisien. Artikel ini akan menyajikan studi kasus nyata tentang bagaimana sebuah aplikasi Node.js yang tadinya memiliki image sebesar 500MB berhasil dioptimalkan menjadi kurang dari 50MB. Kita akan mengungkap rahasia di balik efisiensi ini dan praktik terbaik yang dapat Anda terapkan pada proyek Anda sendiri.
Masalah Awal: Image yang Membengkak
Sebuah tim pengembang menciptakan aplikasi web Node.js. Dockerfile awal mereka terlihat sederhana, namun memiliki beberapa kekurangan fatal yang menyebabkan ukuran image membengkak secara signifikan.
Berikut adalah Dockerfile asli mereka:
Dockerfile
# Dockerfile Awal
FROM node:18
WORKDIR /app
# Salin semua file ke dalam kontainer
COPY . .
# Instal dependensi
RUN npm install
# Jalankan aplikasi
CMD ["node", "server.js"]
Ketika tim membangun image ini, mereka terkejut melihat ukurannya mencapai 500MB. Masalahnya terletak pada beberapa hal:
- Base Image yang Besar:
node:18adalah image yang dibangun di atas Debian, yang memiliki banyak alat dan paket yang tidak dibutuhkan oleh aplikasi. - Keterlibatan Cache Build: Perintah
COPY . .menyalin seluruh proyek (termasuknode_modulesjika ada, file tes, dan lain-lain) sebelumnpm install. Setiap perubahan pada kode akan membatalkan cache untuknpm install, membuatnya harus berjalan dari awal. - File Sampah:
node_modulesyang diinstal berisi banyak dependensi yang hanya dibutuhkan untuk pengembangan, bukan untuk lingkungan produksi.
Solusi 1: Menggunakan Base Image yang Lebih Ringkas
Langkah pertama dalam pengoptimalan adalah beralih ke base image yang lebih kecil. Tim memilih Alpine Linux, sebuah distribusi Linux yang terkenal sangat ringan dan dirancang untuk tujuan container. Versi Node.js yang berbasis Alpine jauh lebih kecil.
Berikut adalah Dockerfile yang diperbarui:
Dockerfile
# Dockerfile Versi 1
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
Hasil: Ukuran image turun drastis menjadi sekitar 250MB. Ini adalah peningkatan yang signifikan, tetapi masih ada ruang untuk perbaikan. node_modules masih berisi banyak file yang tidak dibutuhkan di produksi.
Solusi 2: Mengoptimalkan Proses Build dengan Layer Cache
Tim kemudian menyadari bahwa mereka tidak memanfaatkan caching Docker dengan baik. Mereka memutuskan untuk menyalin file package.json dan package-lock.json terlebih dahulu, menginstal dependensi, dan baru kemudian menyalin sisa kode aplikasi.
Dockerfile
# Dockerfile Versi 2
FROM node:18-alpine
WORKDIR /app
# Salin file manifest terlebih dahulu untuk memanfaatkan cache
COPY package*.json ./
# Instal dependensi
RUN npm install
# Salin sisa kode aplikasi
COPY . .
CMD ["node", "server.js"]
Hasil: Ukuran image tidak berubah, tetap 250MB. Namun, kecepatan build meningkat pesat. Jika hanya ada perubahan pada kode aplikasi (bukan dependensi), Docker akan menggunakan cache untuk langkah RUN npm install, menghemat waktu yang signifikan. Meskipun demikian, ukuran image masih perlu diatasi.
Solusi 3: Menggunakan Multi-Stage Builds untuk Ukuran Minimalis
Ini adalah langkah terpenting yang benar-benar merevolusi proses build mereka. Tim memutuskan untuk menggunakan multi-stage build. Konsepnya adalah menggunakan satu image besar yang berisi semua alat build (npm install, dsb.) dan kemudian menyalin artefak yang dibutuhkan (hanya aplikasi dan dependensi yang diperlukan) ke image kedua yang jauh lebih kecil dan bersih.
Berikut adalah Dockerfile yang dioptimalkan sepenuhnya:
Dockerfile
# Dockerfile Final
# Tahap 1: Builder
# Gunakan base image Node.js yang standar untuk build
FROM node:18 as builder
WORKDIR /app
COPY package*.json ./
# Instal dependensi, termasuk devDependencies.
# Kemudian, bersihkan cache npm.
RUN npm install
COPY . .
# Tahap 2: Runtime
# Gunakan image yang super minimal untuk menjalankan aplikasi
FROM alpine:3.18
# Instal dependensi sistem yang diperlukan oleh aplikasi, jika ada
RUN apk add --no-cache nodejs
WORKDIR /app
# Salin hanya yang diperlukan dari builder stage
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/server.js ./server.js
COPY --from=builder /app/dist ./dist # Asumsi ada file output di dist
# Atur user non-root untuk keamanan
RUN adduser -D myuser
USER myuser
EXPOSE 3000
CMD ["node", "server.js"]
Hasil: Ukuran image final turun drastis menjadi hanya 48.2MB! Ini adalah penurunan yang luar biasa, dari 500MB menjadi kurang dari 50MB.
Analisis Hasil: Mengapa Multi-Stage Builds Begitu Efektif?
- Pemisahan Tanggung Jawab: Tahap pertama (
builder) bertugas membuat aplikasi. Ia memiliki semua alat dan dependensi yang diperlukan untuk proses build. Tahap kedua (runtime) bertugas menjalankan aplikasi. Ia hanya mengandung yang paling minimal yang diperlukan untuk eksekusi. - Menghilangkan Sampah: Semua file dan alat yang hanya dibutuhkan untuk proses instalasi (
npm,devDependencies, cache paket, dsb.) dibiarkan di dalam imagebuilderyang sementara. Image final benar-benar bersih dari file-file yang tidak perlu. - Keamanan: Memisahkan proses build juga memberikan manfaat keamanan. Dengan menggunakan base image minimalis di tahap akhir dan menjalankan aplikasi sebagai non-root user, risiko serangan dapat dikurangi secara signifikan.
Baca juga: Mahasiswa Teknokrat Juara KTI dan Best Expodi PIMPI 2025 IPB University
Pelajaran yang Dapat Diambil
Studi kasus ini memberikan pelajaran berharga tentang pengoptimalan Dockerfile:
- Jangan Takut Menggunakan Multi-Stage Builds: Ini adalah praktik terbaik paling penting untuk mengurangi ukuran image secara drastis, terutama untuk aplikasi yang memiliki banyak dependensi atau memerlukan kompilasi.
- Pilih Base Image yang Tepat: Selalu pertimbangkan ukuran image. Untuk aplikasi Node.js,
node:alpineatau bahkanalpinesaja jika Anda bisa menginstal Node.js sendiri, adalah pilihan yang sangat baik. - Manfaatkan Cache: Selalu susun Dockerfile Anda sedemikian rupa sehingga langkah-langkah yang paling sering berubah berada di bagian paling bawah. Ini akan mempercepat build dan meningkatkan produktivitas.
- Prioritaskan Keamanan: Selalu jalankan aplikasi Anda sebagai pengguna non-root untuk mengurangi risiko keamanan.
Menguasai pengoptimalan Dockerfile adalah keterampilan esensial bagi setiap pengembang DevOps modern. Ini bukan hanya tentang membuat aplikasi berjalan, tetapi memastikan aplikasi tersebut berjalan dengan cepat, ringkas, dan aman. Dengan menerapkan praktik-praktik yang diungkap dalam studi kasus ini, Anda dapat mencapai efisiensi yang luar biasa dan membangun pipeline deployment yang lebih kuat.
Penulis: Fiska Anggraini