Timbangan yang kita angkat di akhir chapter sebelumnya bukan sekadar urusan estetika. Image yang bengkak berarti waktu docker pull yang panjang di setiap deployment, tag yang sama menunggu lebih lama di mesin CI, disk yang terisi lebih cepat di laptop developer, dan permukaan serangan yang lebih luas karena setiap paket tambahan di dalam image adalah pintu yang tidak dibutuhkan. Kabar baiknya: mengecilkan image bukan seni gelap — ia berpola, bisa diukur, dan penerapannya berlapis dari yang termurah sampai yang paling teliti. Chapter ini merangkum tujuh strateginya dalam satu tabel, lalu menguji semuanya lewat studi kasus progresif pada layanan Python baru milik Siti bernama laporan, yang image pertamanya lahir dengan bobot yang memalukan: 1.03GB.
Tujuh Strategi dalam Satu Tabel #
Seluruh taktik pengecilan image yang kita bahas di sini dapat diringkas menjadi tujuh strategi berikut:
| # | Strategi | Wujud konkret | Dampak khas |
|---|---|---|---|
| 1 | Base image ringan | python:3.12-slim, node:22-alpine, alpine:3.20 | puluhan persen hingga sepuluh kali lipat |
| 2 | Multi-stage build | tahap builder terpisah dari tahap runtime | paling dramatis: katalog turun dari 851MB ke 12.6MB |
| 3 | .dockerignore | buang node_modules, .git, folder data dari context | context dan layer ikut ramping |
| 4 | Satu RUN apt + bersihkan cache | --no-install-recommends dan rm -rf /var/lib/apt/lists/* dalam RUN yang sama | puluhan MB per image |
| 5 | COPY spesifik | COPY requirements.txt app.py ./, bukan COPY . . | berkas asing tidak pernah masuk |
| 6 | Unduh rilis terkompresi | arsip tar.gz rilis resmi, git clone --depth 1 | tanpa riwayat repo dan berkas sumber |
| 7 | Hapus tool build | compiler dan make tidak ikut ke runtime | ratusan MB, otomatis lewat strategi 2 |
Dua di antaranya sudah kita kerjakan tuntas di chapter sebelumnya: .dockerignore pada chapter bernama sama, dan multi-stage pada Chapter Multi-Stage Build yang mengecilkan katalog dari 851MB menjadi 12.6MB. Chapter ini fokus menggabungkan seluruhnya dalam satu studi kasus utuh, sekaligus membedah dua jebakan yang membuat strategi lainnya gagal berdampak.
Studi Kasus: Layanan laporan Versi Pertama #
Layanan laporan milik Siti adalah service Python sederhana: app.py menyajikan laporan penjualan sebagai JSON, dengan requirements.txt berisi flask dan gunicorn. Dockerfile pertamanya ditulis dengan naluri paling wajar — ambil Python resmi, salin semuanya, pasang dependensinya:
FROM python:3.12
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]$ cd laporan
$ docker build -t laporan:1.0 .Setelah build selesai, Siti menimbang hasilnya:
$ docker images laporanOutput yang Dihasilkan:
REPOSITORY TAG IMAGE ID CREATED SIZE
laporan 1.0 ce5eff983ff2 4 minutes ago 1.03GBSatu layanan yang kode aslinya di bawah seratus kilobyte ternyata lahir sebagai image 1.03GB. Sebelum menyentuh strategi apa pun, kita ukur dulu dari mana bobot itu datang — docker history memecah total ukuran per instruksi, persis kebiasaan yang kita bangun pada Chapter Image Inspect dan History:
$ docker history laporan:1.0Output yang Dihasilkan:
IMAGE CREATED CREATED BY SIZE COMMENT
ce5eff983ff2 5 minutes ago CMD ["gunicorn" "-b" "0.0.0.0:8000" 0B
<missing> 5 minutes ago RUN pip install -r requirements.txt 7.4MB
<missing> 5 minutes ago COPY . . 12.4MB
<missing> 5 minutes ago WORKDIR /app 0B
<missing> 3 weeks ago CMD ["python3"] 0B
...Mari kita telaah peta bobot di atas:
- Baris-baris bertanggal 3 weeks ago adalah warisan
python:3.12— potongan output yang dipotong itu memuat seluruh layer base: Debian lengkap dengan toolchain, dokumentasi, dan berkasdpkg— sekitar 1.01GB total; baris-baris tersebut dibangun di infrastruktur penerbit image, bukan oleh Dockerfile Siti. COPY . .menyumbang 12.4MB — jauh lebih besar dariapp.pydanrequirements.txtyang hanya puluhan kilobyte; folderlaporanberisidata/penjualan.csvseukuran 12MB bekas eksperimen, dan karena tidak tersaring, berkas itu ikut tersalin menjadi layer — kecelakaan persis yang dicegah.dockerignorepada chapter bernama sama.RUN pip installhanya 7.4MB — dua paket Siti memang ringan; hampir seluruh kegemukan image ini bukan kesalahan Siti, melainkan pilihan base image.
Kesimpulan pengukurannya jelas: sasaran pertama adalah strategi 1 — ganti base image. Strategi lain baru terasa setelah fondasinya benar.
Strategi 1: Base Ringan, dari 1.03GB ke 133MB #
Image python resmi hadir dalam beberapa varian, dan pilihan varian inilah lompatan terbesar studi kasus kita. Siti mengganti FROM python:3.12 dengan python:3.12-slim — Debian yang dipangkas hingga menyisakan runtime inti — dan sekaligus menerapkan dua perbaikan kecil yang sudah kita kenal: COPY spesifik menggantikan COPY . ., serta --no-cache-dir pada pip supaya cache unduhan (yang beralamat di /root/.cache/pip, persis tabel Chapter *Cache Mount`) tidak ikut menjadi layer:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py ./
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]$ docker build -t laporan:1.1 .Output yang Dihasilkan:
REPOSITORY TAG IMAGE ID CREATED SIZE
laporan 1.1 a38741edc50d 30 seconds ago 133MBSatu pergantian baris FROM plus dua perbaikan salinan menurunkan image dari 1.03GB ke 133MB — hampir delapan kali lipat. Sebuah catatan jujur perlu menyertai angka ini: image Python yang benar-benar berisi CPython tidak mungkin belasan megabyte. Runtime CPython saja menyumbang sekitar 50MB takterkompresi di varian alpine, dan klaim “image Python 15MB” yang sesekali beredar hampir selalu mengacu ukuran unduhan terkompresi di registry — sekitar 20MB untuk varian alpine — bukan ukuran di disk yang dilaporkan docker images. Memahami dua ukuran yang berbeda ini menyelamatkan kita dari target yang mustahil.
Jebakan rm di Layer Berbeda #
Strategi 4 sering dipahami setengah lalu: “hapus cache apt setelah selesai”. Perhatikan dua bentuk Dockerfile berikut — yang kiri tampak rapi tetapi tidak mengecilkan apa pun:
RUN apt-get update && apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*Kedua instruksi itu memang menghapus berkas indeks apt dari filesystem container, tetapi tidak dari image. Ingat bahwa image adalah tumpukan layer: RUN pertama menghasilkan layer berisi curl beserta seluruh hasil apt-get update, dan layer itu sudah tersimpan permanen; RUN kedua membuat layer baru yang hanya mencatat penghapusan — total kedua layer tetap memuat semua byte hasil instalasi. Riwayatnya membuktikan:
$ docker history contoh-apt:terpisahOutput yang Dihasilkan:
IMAGE CREATED CREATED BY SIZE COMMENT
0d3b6f9c2e71 2 minutes ago CMD ["curl" "--version"] 0B
<missing> 2 minutes ago RUN rm -rf /var/lib/apt/lists/* 0B
<missing> 2 minutes ago RUN apt-get update && apt-get instal 43.1MB
...Baris rm menyumbang 0B penghapusan pada ukuran total, dan 43.1MB instalasi tetap utuh di layer di bawahnya. Satu-satunya jalan: gabungkan semuanya dalam satu RUN sehingga pembersihan terjadi sebelum layer dibekukan:
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*Hasilnya satu layer 11.9MB — cache indeks dan paket “recommended” tidak pernah sempat membeku. Bentuk panjang berkelanjutan baris ini bisa ditulis lebih lapang memakai heredoc Dockerfile modern (aktif dengan menambahkan directive parser # syntax=docker/dockerfile:1 pada baris pertama Dockerfile):
RUN <<EOF
apt-get update
apt-get install -y --no-install-recommends curl
rm -rf /var/lib/apt/lists/*
EOFMari kita telaah tiga pilar pola satu-RUN ini:
--no-install-recommends— Debian secara bawaan memasang paket “rekomendasi” yang sering tidak dibutuhkan; flag ini memangkasnya sejak awal.rm -rf /var/lib/apt/lists/*— indeks paket hasilapt-get updatetidak dibutuhkan runtime; menghapusnya di dalamRUNyang sama berarti layer tidak pernah merekamnya.- Heredoc
RUN <<EOF— murni kenyamanan keterbacaan: tiga baris perintah tetap menjadi satu instruksiRUNsatu layer, tanpa rantai&&yang memanjang.
Strategi 2: Multi-Stage dan Alpine, ke 58.2MB #
Langkah terakhir Siti mengikuti pola yang sudah dibuktikan katalog pada Chapter Multi-Stage Build: tahap build terpisah dari tahap runtime, kali ini dengan base python:3.12-alpine:
FROM python:3.12-alpine AS build
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir --prefix=/out -r requirements.txt
FROM python:3.12-alpine
WORKDIR /app
COPY --from=build /out/ /usr/local/
COPY app.py ./
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]$ docker build -t laporan:1.2 .Output yang Dihasilkan:
REPOSITORY TAG IMAGE ID CREATED SIZE
laporan 1.2 635a30bb4a9f 40 seconds ago 58.2MBMari kita telaah perjalanan progresif ketiganya dalam satu tabel:
| Iterasi | Perubahan kunci | Ukuran |
|---|---|---|
laporan:1.0 | python:3.12 penuh + COPY . . | 1.03GB |
laporan:1.1 | python:3.12-slim + COPY spesifik + --no-cache-dir | 133MB |
laporan:1.2 | multi-stage + python:3.12-alpine | 58.2MB |
Pada tahap build, pip install --prefix=/out memasang dependensi ke folder sementara /out alih-alih ke site-packages builder; tahap runtime lalu menyalin hanya isi /out itu ke atas base alpine yang sama. Yang tidak ikut pindah: seluruh jejak pip, cache, dan berkas sementara tahap build — mereka berhenti di layer builder yang tidak pernah menjadi bagian image final. Nilai --prefix di sini berfungsi layaknya COPY --from=builder /app/server pada katalog: garis batas tegas antara “perlu untuk membangun” dan “perlu untuk berjalan”.
Dua strategi terakhir pada tabel bekerja diam-diam di balik contoh di atas: COPY requirements.txt dan COPY app.py adalah wujud strategi 5 (salinan spesifik — data/penjualan.csv kini tertinggal di context tanpa perlu .dockerignore tambahan), sementara strategi 6 dan 7 sudah terwakili oleh pola katalog: unduhan terkompresi untuk aset eksternal dan tool build yang berhenti di tahap builder. Bila suatu hari laporan butuh biner pihak ketiga, ambil arsip rilis resminya dalam satu RUN yang juga mengekstrak dan membersihkan — bukan git clone yang membawa seluruh riwayat repo, kebiasaan yang kita singgung pada Chapter Secret dan SSH Mount.
Image laporan akhirnya ramping 58.2MB dan siap di-push. Namun tim audit internal kemudian mengajukan pertanyaan yang tidak bisa dijawab dengan ukuran: kalau Siti membangun laporan:1.2 lagi besok dari kode yang sama, apakah image ID-nya akan sama? Jawaban jujurnya — belum — dan itulah urusan chapter penutup section ini: reproducible build.
Referensi #
- Dockerfile Best Practices — panduan resmi pola apt, COPY, dan urutan instruksi yang menjaga image ramping.
- Dockerfile Reference
— sintaks heredoc
RUN <<EOFdan seluruh instruksi yang dipakai studi kasus ini. - Python Image — daftar varian resmi image Python beserta perbedaan ukurannya.