Image Docker Distroless: Container Lebih Kecil dan Aman

Foto oleh fringedbenefit on flickr
Image distroless adalah base container minimal yang hanya menyertakan aplikasimu dan dependensi runtime-nya, seperti runtime bahasa, shared library, dan sertifikat CA. Image ini secara sengaja tidak menyertakan shell, package manager, dan utilitas OS standar, itulah yang membuatnya jauh lebih kecil dan lebih sedikit jumlah kerentanannya dibanding base Debian atau Alpine penuh.
Alpine memang sudah lebih kecil dibanding image OS penuh, tapi tetap membawa shell dan package manager, dan musl libc miliknya bisa diam diam merusak native addon Node atau wheel Python yang mengharapkan glibc. Image distroless berbasis glibc menghilangkan shell dan package manager sepenuhnya sambil tetap menjaga kompatibilitas glibc penuh, yang biasanya berarti lebih sedikit kejutan untuk dependensi yang dikompilasi.
Ada tiga cara praktis yang efektif: pakai varian bertanda debug dari image distroless yang sama saat development, yang membawa shell busybox; pakai perintah docker debug milik Docker sendiri, yang menyambungkan toolbox interaktif ke container atau image yang berjalan tanpa mengubahnya; atau reproduksi masalahnya secara lokal pada stage build di Dockerfile-mu, yang masih punya shell lengkap.
Google merilis varian distroless resmi untuk Node.js, Python, Java, dan base statis untuk bahasa terkompilasi seperti Go, dan ada juga varian yang dirawat komunitas untuk bahasa lain. Kalau aplikasimu memanggil binary sistem saat runtime, seperti memanggil ffmpeg atau imagemagick lewat child process, kamu perlu menyertakan binary tersebut ke dalam image secara eksplisit karena distroless tidak akan menyediakannya secara default.
Ya, karena vulnerability scanner mencocokkan versi paket yang terpasang dengan database CVE, dan image distroless memang punya jauh lebih sedikit paket terpasang untuk dicocokkan. Menghilangkan package manager dan shell juga menutup cara paling umum yang dipakai penyerang untuk berpindah dari eksekusi kode menjadi akses interaktif di dalam container yang berhasil disusupi.

Foto oleh fringedbenefit on flickr
Setiap layer yang kamu kirim dalam image Docker produksi adalah permukaan serangan. Base image Debian atau Ubuntu penuh membawa shell, package manager, coreutils, dan puluhan library yang tidak pernah disentuh aplikasimu, dan semuanya menjadi daftar temuan setiap kali vulnerability scanner dijalankan terhadap registry-mu.
Image distroless menghilangkan semua itu, hanya menyisakan runtime bahasa dan shared library yang benar benar dipakai binary-mu. Dipadukan dengan Dockerfile multi-stage, hasilnya adalah image produksi yang lebih kecil, lebih cepat diunduh, dan jauh lebih sedikit temuan pada laporan CVE. Artikel ini membahas cara kerja distroless, cara membangunnya dengan Dockerfile multi-stage, angka ukuran nyata, cara debug container tanpa shell, dan kapan pendekatan ini tidak tepat dipakai.
Image distroless, yang dirawat oleh proyek container tools milik Google, dibangun di atas satu gagasan: masukkan hanya aplikasimu dan dependensi runtime-nya ke dalam image akhir. Semua hal yang biasanya dibawa distribusi Linux untuk pemakaian interaktif dihilangkan secara sengaja.
Google merilis varian distroless per bahasa: nodejs, python3, java, dan base statis untuk binary Go. Pilih yang cocok dengan runtime-mu daripada mencoba membangun sendiri dari nol di hari pertama.
Multi-stage build memungkinkan satu Dockerfile memakai beberapa instruksi FROM, satu per stage. Stage pertama mendapat toolchain lengkap: compiler, npm, dependensi development, semua yang dibutuhkan untuk menghasilkan artefak build. Stage terakhir dimulai dari nol dengan base distroless dan hanya menyalin hasil kompilasi, menggunakan instruksi COPY dengan flag from yang menunjuk ke stage sebelumnya lewat nama.
# Stage 1: build the app with full Node.js tooling
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: runtime — no shell, no package manager, no npm
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./package.json
USER nonroot
EXPOSE 3000
CMD ["dist/server.js"]Tidak ada satupun dari stage build yang bocor ke image akhir kecuali kamu menyalinnya secara eksplisit. SDK, berkas sumber TypeScript, dependensi development, dan artefak build sementara tetap tinggal di stage yang dibuang begitu build selesai. Beri nama tiap stage dengan klausa AS agar Dockerfile tetap masuk akal kalau instruksinya diurutkan ulang nanti, dan pertimbangkan flag target saat development lokal supaya kamu bisa membangun dan memeriksa stage build saja tanpa menunggu langkah penyalinan akhir.
Angkanya berbeda beda tergantung aplikasi, tapi pola di bawah ini konsisten pada layanan Node.js. Ini adalah perkiraan ukuran image akhir untuk API server kecil yang sama, dibangun dengan tiga cara berbeda.
| Base image | Perkiraan ukuran | Catatan |
|---|---|---|
| node:20 | ~1.1 GB | Debian penuh, termasuk seluruh toolchain build yang tidak pernah dibutuhkan aplikasi yang berjalan |
| node:20-alpine | ~180 MB | Berbasis musl libc, lebih kecil tapi kadang membuat native addon Node yang mengharapkan glibc jadi rusak |
| gcr.io/distroless/nodejs20-debian12 | ~120 MB | Berbasis glibc, tanpa shell atau package manager, paling mendekati jejak runtime sebenarnya |
Penurunan ukuran ini penting untuk waktu unduh di setiap deploy dan setiap start pod autoscale, tapi kemenangan yang lebih besar biasanya ada di laporan vulnerability scan. Semakin sedikit paket berarti semakin sedikit CVE yang bisa ditemukan scanner seperti Trivy atau Grype, karena memang tidak ada apa apa yang terpasang untuk dicocokkan dengan database CVE sejak awal.
Pertama kali perintah exec masuk lewat bash gagal dengan error entrypoint tidak ditemukan pada container distroless, rasanya membingungkan. Memang tidak ada bash di image itu, dan tidak akan pernah ada, itulah inti dari distroless. Tetap ada tiga cara praktis untuk melakukan debug.
# Attach an interactive debug shell to a running distroless container
docker debug my-running-container
# Or inspect the image itself, before running it
docker debug gcr.io/distroless/nodejs20-debian12Perubahan yang dibuat lewat shell debug yang tersambung ke container yang berjalan tidak pernah tersimpan kembali ke image. Kalau kamu memasang tool atau mengedit berkas untuk menguji sebuah teori, catat apa yang kamu pelajari langsung di Dockerfile, karena container akan kembali ke kondisi semula begitu sesi debug berakhir.
Vulnerability scanner bekerja dengan mencocokkan versi paket yang terpasang terhadap database CVE yang dikenal. Base image OS penuh membawa ratusan paket yang terpasang untuk pemakaian umum, sebagian besar tidak pernah dijalankan aplikasimu tapi tetap saja ditandai begitu CVE baru muncul untuk, misalnya, versi lama sebuah text editor yang tidak pernah dibuka siapapun.
Distroless bukan tanpa biaya. Tim yang baru memakainya kehilangan waktu di minggu pertama karena kebiasaan debug yang familiar seperti masuk lewat bash berhenti berfungsi, dan dependensi runtime apapun yang dipanggil aplikasimu saat berjalan, seperti memanggil binary sistem lewat child process, harus disertakan secara eksplisit karena tidak akan ada secara default. Kalau timmu deploy cepat dan debug lewat log serta health check level aplikasi ketimbang masuk langsung ke dalam container yang berjalan, biaya transisinya kecil. Kalau proses deploy timmu sangat bergantung pada masuk ke container yang berjalan untuk memeriksa isinya, sisihkan waktu untuk menyesuaikan workflow sebelum menerapkannya secara luas.
Urutan rollout yang baik: ubah satu layanan dengan traffic rendah ke Dockerfile multi-stage distroless lebih dulu, pastikan logging dan health check-mu memberi sinyal yang cukup tanpa akses shell, baru kemudian terapkan pola ini ke sisa layanan lainnya.
Distroless dipadukan dengan multi-stage build adalah salah satu perubahan berdampak paling besar yang bisa kamu lakukan pada pipeline deploy berbasis Docker. Penurunan ukurannya memang bagus, tapi keuntungan sesungguhnya adalah image produksi yang hampir tidak menyisakan apapun bagi penyerang untuk dipakai setelah masuk, serta laporan vulnerability scan yang benar benar mencerminkan jejak runtime-mu, bukan seluruh distribusi Linux serba guna yang sejak awal tidak pernah kamu butuhkan.