Seorang junior developer di tim seorang editor senior pernah mendorong sebuah Docker image berukuran 1.2GB ke registry. CI pipeline pun macet selama 11 menit. Ketika diperiksa, image tersebut membengkak hanya untuk aplikasi web Python dengan tiga dependensi. Kejadian ini tidak asing bagi banyak orang: apt‑get install yang tidak pernah bersih, cache pip yang terlupakan, dan janji “akan dibersihkan nanti” yang tidak pernah ditepati.
Masalah utama gambar yang besar adalah kebocoran dependensi build ke stage runtime. Untuk mengkompilasi sebuah paket, Anda memerlukan gcc, tetapi Anda tidak memerlukannya saat menjalankan aplikasi. Cache manajer paket, seperti apt‑get update, mengunduh indeks yang tidak pernah digunakan lagi. Setiap perintah RUN membuat layer baru, dan berkas yang dihapus di layer kelima tetap memakan ruang di layer ketiga. Bahkan menggunakan ubuntu:latest sebagai base image sudah menghabiskan 77 MB sebelum Anda mulai menulis kode.
Contoh nyata Dockerfile aplikasi Python di produksi adalah:
FROM python:3.11 RUN apt-get update && apt-get install -y \\ gcc g++ make \\ libpq-dev libffi-dev \\ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . RUN pip install -e . CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Hasilnya adalah image sebesar 1.18 GB. Image python:3.11 saja sudah sekitar 920 MB; menambahkan gcc, g++, make, dan wheel yang dikompilasi membuat totalnya melebihi 1 GB. Sementara itu, kode aplikasi sebenarnya mungkin hanya 15 MB.
Solusi yang diterapkan adalah multi‑stage builds, yang memisahkan stage build dan stage runtime dalam satu Dockerfile. Contohnya:
# Stage 1: Build FROM python:3.11-slim AS builder RUN apt-get update && apt-get install -y --no-install-recommends \\ gcc g++ make libpq-dev libffi-dev \\ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2: Runtime FROM python:3.11-slim COPY --from=builder /root/.local /root/.local RUN apt-get update && apt-get install -y --no-install-recommends \\ libpq5 \\ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY . . ENV PATH="/root/.local/bin:$PATH" CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Ukuran image turun menjadi 210 MB—lebih kecil 5.6 kali. Perubahan yang dilakukan: beralih dari python:3.11 (~920 MB) ke python:3.11‑slim (~120 MB), menjaga tools build (gcc, g++, make) di stage builder, menggunakan pip install --user, dan apt‑get --no-install-recommends untuk menghindari paket yang tidak diperlukan.
Optimasi lebih lanjut dapat dilakukan dengan Alpine sebagai base image. Menggunakan python:3.11‑alpine menghasilkan image sekitar 85 MB. Trade‑off-nya adalah Alpine menggunakan libc musl bukan glibc; ekstensi C seperti psycopg2 harus diuji secara menyeluruh sebelum digunakan di produksi.
Untuk bahasa yang dikompilasi seperti Go atau Rust, distroless menawarkan solusi yang lebih ekstrem: image hanya berisi binary dan CA certificate bundle, tanpa shell atau package manager. Contoh Go server berukuran sekitar 14 MB.
Perbandingan sebelum dan sesudah:
| Strategi | Ukuran Image | Waktu Build | Surface Keamanan | |----------|--------------|-------------|------------------| | python:3.11 | 1.18 GB | 4 menit | Tinggi (780+ paket) | | python:3.11‑slim + multi-stage | 210 MB | 3 menit | Menengah (120+ paket) | | python:3.11‑alpine + multi-stage | 85 MB | 2.5 menit | Rendah (40+ paket) |
Ini adalah pengurangan ukuran 93 %. CI pipeline yang semula butuh 11 menit turun menjadi 90 detik, terutama karena transfer data 85 MB jauh lebih cepat daripada 1.2 GB.
Ada tiga masalah yang perlu diwaspadai:
1. **Resolusi DNS di Alpine** – python:3.11‑alpine tidak menyertakan nss secara default, sehingga bisa merusak resolusi hostname di beberapa setup Kubernetes. Solusinya: `RUN apk add --no-cache nss` (tambah ~2 MB). 2. **pip install --user dan PATH** – jika ENV PATH="/root/.local/bin:$PATH" terlupa, container runtime tidak akan menemukan uvicorn atau CLI tool lainnya yang diinstal via pip. Build sukses, tetapi deploy gagal saat runtime. 3. **Urutan COPY** – Docker menyimpan cache per layer. Letakkan COPY requirements.txt dan pip install sebelum COPY . .. Perubahan kecil pada kode sumber tidak akan membatalkan cache instalasi dependensi, sehingga setiap build tetap efisien.
ROI yang sebenarnya bukan hanya ukuran yang lebih kecil. Image yang lebih kecil berarti:
- **Deploy lebih cepat** – Pod Kubernetes yang mengambil image 85 MB selesai dalam hitungan detik, sementara image 1.2 GB bisa memakan menit. - **Biaya registry lebih rendah** – Penyimpanan AWS ECR dikenakan $0.10/GB/bulan. Untuk 50 image, penghematan bisa mencapai puluhan dolar per bulan. - **Surface serangan lebih kecil** – Lebih sedikit paket berarti lebih sedikit CVE. - **Pengembang lebih bahagia** – CI yang berjalan dalam hitungan menit, bukan puluhan menit.
Di Indonesia, banyak startup teknologi seperti Traveloka, Tokopedia, Gojek, dan Bukalapak menggunakan containerisasi untuk layanan mikro mereka. Bandwidth yang terbatas di beberapa wilayah membuat image yang lebih kecil sangat berharga, karena mengurangi waktu pull dan menghemat kuota transfer data. Selain itu, biaya penyimpanan di cloud provider seperti Alibaba Cloud, AWS, dan Google Cloud di Indonesia kini dihitung per GB, sehingga pengurangan 1 GB per image bisa menghemat jutaan rupiah per tahun.
Ke depan, tren menuju base image yang minimal akan terus berkembang. Praktik terbaik mencakup audit rutin Dockerfile, penggunaan multi-stage builds, dan pengujian menyeluruh saat beralih ke Alpine atau distroless. Bagi tim pengembang Indonesia, mengadopsi teknik ini bukan hanya tentang efisiensi, tetapi juga tentang daya saing di pasar yang semakin bergantung pada pengiriman perangkat lunak yang cepat dan aman.
Dengan menerapkan tips ini, siapa pun dapat menikmati pipeline CI yang lebih cepat, biaya operasional yang lebih rendah, dan pengalaman pengembangan yang lebih menyenangkan.