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:

#StrategiWujud konkretDampak khas
1Base image ringanpython:3.12-slim, node:22-alpine, alpine:3.20puluhan persen hingga sepuluh kali lipat
2Multi-stage buildtahap builder terpisah dari tahap runtimepaling dramatis: katalog turun dari 851MB ke 12.6MB
3.dockerignorebuang node_modules, .git, folder data dari contextcontext dan layer ikut ramping
4Satu RUN apt + bersihkan cache--no-install-recommends dan rm -rf /var/lib/apt/lists/* dalam RUN yang samapuluhan MB per image
5COPY spesifikCOPY requirements.txt app.py ./, bukan COPY . .berkas asing tidak pernah masuk
6Unduh rilis terkompresiarsip tar.gz rilis resmi, git clone --depth 1tanpa riwayat repo dan berkas sumber
7Hapus tool buildcompiler dan make tidak ikut ke runtimeratusan 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:

dockerfile
FROM python:3.12
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
bash
$ cd laporan
$ docker build -t laporan:1.0 .

Setelah build selesai, Siti menimbang hasilnya:

bash
$ docker images laporan

Output yang Dihasilkan:

text
REPOSITORY   TAG   IMAGE ID       CREATED             SIZE
laporan      1.0   ce5eff983ff2   4 minutes ago       1.03GB

Satu 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:

bash
$ docker history laporan:1.0

Output yang Dihasilkan:

text
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:

  1. 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 berkas dpkg — sekitar 1.01GB total; baris-baris tersebut dibangun di infrastruktur penerbit image, bukan oleh Dockerfile Siti.
  2. COPY . . menyumbang 12.4MB — jauh lebih besar dari app.py dan requirements.txt yang hanya puluhan kilobyte; folder laporan berisi data/penjualan.csv seukuran 12MB bekas eksperimen, dan karena tidak tersaring, berkas itu ikut tersalin menjadi layer — kecelakaan persis yang dicegah .dockerignore pada chapter bernama sama.
  3. RUN pip install hanya 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:

dockerfile
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"]
bash
$ docker build -t laporan:1.1 .

Output yang Dihasilkan:

text
REPOSITORY   TAG   IMAGE ID       CREATED             SIZE
laporan      1.1   a38741edc50d   30 seconds ago      133MB

Satu 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:

dockerfile
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:

bash
$ docker history contoh-apt:terpisah

Output yang Dihasilkan:

text
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:

dockerfile
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):

dockerfile
RUN <<EOF
apt-get update
apt-get install -y --no-install-recommends curl
rm -rf /var/lib/apt/lists/*
EOF

Mari kita telaah tiga pilar pola satu-RUN ini:

  1. --no-install-recommends — Debian secara bawaan memasang paket “rekomendasi” yang sering tidak dibutuhkan; flag ini memangkasnya sejak awal.
  2. rm -rf /var/lib/apt/lists/* — indeks paket hasil apt-get update tidak dibutuhkan runtime; menghapusnya di dalam RUN yang sama berarti layer tidak pernah merekamnya.
  3. Heredoc RUN <<EOF — murni kenyamanan keterbacaan: tiga baris perintah tetap menjadi satu instruksi RUN satu 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:

dockerfile
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"]
bash
$ docker build -t laporan:1.2 .

Output yang Dihasilkan:

text
REPOSITORY   TAG   IMAGE ID       CREATED             SIZE
laporan      1.2   635a30bb4a9f   40 seconds ago      58.2MB

Mari kita telaah perjalanan progresif ketiganya dalam satu tabel:

IterasiPerubahan kunciUkuran
laporan:1.0python:3.12 penuh + COPY . .1.03GB
laporan:1.1python:3.12-slim + COPY spesifik + --no-cache-dir133MB
laporan:1.2multi-stage + python:3.12-alpine58.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 <<EOF dan seluruh instruksi yang dipakai studi kasus ini.
  • Python Image — daftar varian resmi image Python beserta perbedaan ukurannya.