# Docker Build Cache: Deploy PaaS Lebih Cepat dari Builder Dingin

> Builder di platform dibuat khusus untuk deploy Anda lalu dihapus, jadi semua layer dingin kecuali Anda mengaturnya. Urutan layer, build context, cache mount, dan registry cache yang benar-benar ikut berpindah.

URL: https://www.matthewswong.com/id/blog/docker-build-cache-paas-deploy-speed/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-09-23
- Updated: 2026-09-23
- Category: DevOps

> **Key takeaway:** Builder PaaS atau CI dibuat baru pada setiap deploy, jadi layer cache Docker mulai kosong kecuali Anda mengekspornya. Susun Dockerfile agar dependency terpasang sebelum source disalin, jaga build context tetap kecil lewat file dockerignore, dan dorong registry-backed cache dengan cache-to serta cache-from supaya builder dingin berikutnya mulai dalam keadaan hangat.

Saya mengubah satu string di komponen footer lalu push. Build log di platform melakukan hal yang sama seperti biasa: menarik base image, menjalankan install dependency penuh, meng-compile seluruh aplikasi, baru menyalakan container. Tidak ada bagian dari satu string itu yang menuntut install ulang apa pun, dan deploy-nya tetap selama deploy sebelumnya.

Tulisan ini membahas kenapa itu terjadi khusus di builder milik platform, dan apa yang perlu diubah supaya perubahan kecil hanya membayar deploy kecil: urutan Dockerfile, ukuran build context, apa yang bertahan dan tidak bertahan dari sebuah cache mount, serta satu pasang flag yang membuat builder baru mulai hangat. Semua flag, nilai default, dan nama mode di bawah dicek ke dokumentasi Docker, bukan diandalkan dari ingatan.

## Builder Ephemeral Selalu Mulai dari Keadaan Dingin

Aturan cache Docker sudah umum diketahui, tetapi itu baru separuh cerita. Dokumentasinya menyebutkan dengan lugas: perubahan apa pun pada perintah instruksi RUN membatalkan layer itu, perubahan pada file yang disalin lewat COPY atau ADD membatalkan layer miliknya, dan begitu satu layer batal, semua layer setelahnya ikut batal. Itulah aturan yang biasa kita optimalkan di laptop, tempat layer dari build sebelumnya masih tersimpan di builder lokal.

PaaS atau CI runner tidak bekerja seperti itu. Builder-nya dibuat untuk deploy Anda lalu dibuang setelahnya, jadi tidak ada layer lama yang bisa dibatalkan — memang tidak ada apa-apa di sana. Semua instruksi dijalankan. Itu sebabnya Dockerfile yang rebuild dalam hitungan detik di mesin Anda butuh menit di platform, dan kenapa cepat di lokal bukan bukti apa pun. Di builder ephemeral, cache bukan sesuatu yang Anda simpan, melainkan sesuatu yang Anda kirim masuk dan keluar dengan sengaja.

## Susun Dockerfile agar Perubahan Source Tak Menyentuh Install

Ini kemenangan tunggal terbesar dan biayanya hanya perhatian. Panduan optimasi Docker cukup satu kalimat: karena sebuah perubahan memaksa rebuild pada langkah-langkah sesudahnya, usahakan langkah yang mahal muncul di bagian awal Dockerfile. Di image Node atau Next.js, langkah mahal itu adalah install dependency, sedangkan yang berubah lima puluh kali sehari adalah source Anda.

Versi yang salah menyalin semuanya lebih dulu, baru install. Input layer COPY itu mencakup seluruh file di repository, jadi mengubah satu komponen mengubah layer tersebut, yang membatalkan install di bawahnya, lalu package manager kembali me-resolve dan mengunduh seluruh pohon dependency hanya karena perubahan teks. Versi yang benar menyalin manifest dan lockfile saja, install, baru menyalin source. Sekarang layer install hanya punya dua input dan dipakai ulang sampai ada dependency yang benar-benar berubah.

Perlu tepat soal arti berubah di sini. Layer dikunci pada file yang disalin ke dalamnya, bukan pada niat Anda, jadi memformat ulang manifest tanpa menyentuh satu dependency pun tetap membatalkan install. Itu bukan bug yang perlu diakali — justru itu alasan COPY dua file layak ditulis sebagai baris tambahan.

> Salin lockfile dengan nama eksplisit, bukan dengan glob. Pola seperti package bintang titik json cocok dengan lockfile hari ini, tetapi juga cocok dengan apa pun berawalan package yang ditambahkan orang tahun depan, sehingga input layer install melebar diam-diam tanpa disadari. Dua nama file adalah kontrak yang bisa dibaca sekilas.

## File dockerignore Itu Alat Cache, Bukan Sekadar Kerapian

Setiap path yang sampai ke builder adalah path yang bisa membatalkan langkah copy, dan secara default seluruh direktori ikut terkirim. Dokumentasinya menjelaskan build context sebagai kumpulan file yang bisa diakses build Anda, diproses secara rekursif sehingga semua isi direktori lokal ikut, dan menjelaskan file dockerignore sebagai penyaring yang membuang file yang cocok dari context sebelum dikirim ke builder. Tanpa itu, empat hal berikut rutin merusak cache karena alasan yang sama sekali tidak berhubungan dengan kode Anda:

- Direktori git — setiap commit, fetch, dan rebase menulis ulang isinya, jadi context berbeda pada benar-benar setiap build.
- node_modules — hasil install lokal Anda, dibangun untuk arsitektur Anda, diunggah ke builder hanya untuk ditimpa oleh langkah install.
- Hasil build seperti .next, dist, atau build — artefak kemarin ikut disalin supaya build hari ini memproduksinya lagi.
- File env lokal dan log — berbeda di setiap mesin, dan memang tidak layak ikut tertanam di dalam layer.

Ada biaya kedua yang baru terasa di platform. Build context diunggah ke builder sebelum build dimulai, jadi node_modules yang lolos adalah masalah cache sekaligus transfer yang Anda bayar pada setiap deploy. Di laptop, transfer itu cuma pembacaan filesystem dan tidak pernah Anda sadari.

## Di Multi-Stage Build, Ukuran Tak Penting tetapi Cacheability Penting

Multi-stage build biasanya dijual lewat argumen ukuran image, dan bagian itu memang benar. Anda memakai beberapa statement FROM, lalu menyalin artefak tertentu dari satu stage ke stage lain dan meninggalkan semua yang tidak Anda inginkan di image akhir, sehingga runtime image berangkat tanpa compiler, dev dependency, maupun source.

Separuh yang penting untuk kecepatan deploy justru berbeda: stage yang tidak dikirim siapa pun tetap harus dibangun seseorang. Builder stage tidak memakan apa pun saat runtime, tetapi menagih install penuh pada setiap deploy kalau layer-nya tidak ter-cache. Jadi perlakukan build stage dengan disiplin urutan yang sama seperti file satu stage — manifest, install, lalu source — dan beri nama stage dengan AS, karena copy dari stage bernama tetap benar ketika ada orang menyisipkan stage di atasnya, sedangkan copy dari stage nomor nol tidak.

*Band paling bawah adalah inti masalahnya: taruh copy source di atas install, maka setiap layer di bawahnya dikunci pada seluruh file di repository.*

## Cache Mount Membuat Install Murah, tetapi Tak Bertahan dari Builder

Cache mount memberi satu instruksi RUN sebuah direktori yang hidup di luar layer. Referensi Dockerfile menjelaskannya sebagai cara build container menyimpan direktori cache untuk compiler dan package manager, yang isinya bertahan antar pemanggilan builder tanpa membatalkan instruction cache. Arahkan ke store milik package manager, maka layer install yang terpaksa dibangun ulang pun hanya mengunduh yang benar-benar berubah. Opsi sharing default-nya shared, yang mengizinkan penulis bersamaan; locked menahan penulis kedua sampai penulis pertama melepas mount.

Catatan pentingnya adalah alasan bagian ini bukan bagian terakhir. Cache mount bukan sebuah layer. Ia tidak ikut di image yang Anda push dan tidak ikut di cache yang Anda ekspor — ia tinggal di storage milik builder. Garbage collection bawaan BuildKit memperlakukannya sebagai ephemeral: policy pertama membuang build cache tak terpakai bertipe exec.cachemount, bersama local context dan git checkout, begitu usianya lewat 48 jam dan melewati ambang batas. Di builder yang dihapus setelah deploy selesai, 48 jam itu kelewat murah hati.

> Jangan pernah menulis build yang bergantung pada isi direktori cache. Referensi Dockerfile menyatakan tegas bahwa cache mount hanya untuk performa dan build harus tetap jalan dengan isi direktori cache apa pun, karena build lain bisa menimpa file di sana atau garbage collection bisa membersihkannya. Build yang hanya berhasil saat hangat adalah build yang gagal tepat di hari Anda paling membutuhkannya.

## Registry-Backed Cache yang Membuat Builder Dingin Jadi Hangat

Semua di atas membuat rebuild lebih murah. Bagian ini yang membuat builder baru bisa melewati rebuild sama sekali. BuildKit menyimpan hasil build di internal cache miliknya secara otomatis, tetapi dokumentasinya jelas bahwa external cache storage tidak otomatis: semua cache storage backend harus diekspor secara eksplisit dan diimpor secara eksplisit. Dua flag melakukan itu, dan registry backend adalah yang bisa dipakai di mana pun ada container registry.

Parameter mode inilah yang sering terlewat. Default-nya min, yang hanya menyimpan layer yang diekspor ke image hasil akhir; mode=max menyimpan semua layer, termasuk layer dari langkah antara. Untuk multi-stage build, itu bedanya antara mengimpor layer runtime yang toh hanya butuh beberapa detik untuk dibangun ulang, dan mengimpor layer builder stage yang tadinya akan menagih install penuh. Kalau Anda membangun secara multi-stage — dan sebaiknya begitu — Anda ingin max.

Trade-off-nya nyata dan tidak selalu berpihak pada Anda, karena mengimpor cache adalah operasi jaringan: cache image harus ditarik dulu sebelum build bisa membacanya. Dihitung sebagai aritmetika, bukan sebagai hasil pengukuran, kalau layer dependency yang Anda lewati tadinya butuh tiga menit untuk install dan blob cache-nya butuh empat puluh detik untuk ditarik, Anda jelas untung; kalau layer yang dilewati cuma copy empat file selama dua belas detik, Anda justru memperlambat deploy sekaligus menambah tagihan registry. Ekspor cache untuk layer yang mahal dan stabil, dan tak usah repot untuk yang murah.

*Hanya panel ketiga yang hidup lebih lama dari builder-nya, dan itulah sebabnya cache mount sendirian meninggalkan deploy di platform sedingin awalnya.*

## Baca Build Log, Jangan Menebak Layer Mana yang Rusak

Semua klaim di atas bisa diuji lewat build output, yang mencetak CACHED di depan langkah yang dipakai ulang. Itu saja diagnostiknya. Cari langkah pertama di log yang tidak bertanda CACHED: input langkah itulah yang berubah, dan semua yang di bawahnya memang akan dijalankan ulang sepintar apa pun sisa Dockerfile Anda.

Disiplin yang membuat log itu berguna adalah mengubah satu hal dalam satu waktu lalu build ulang, yang terdengar sepele dan justru paling sering dilewati:

1. Build dua kali tanpa mengubah apa pun. Semua langkah setelah base image seharusnya mencetak CACHED pada run kedua. Kalau ada yang tidak, instruksi itu tidak deterministik — install paket tanpa versi terkunci, timestamp, atau fetch jaringan.
2. Ubah satu file source lalu build ulang, dan cari langkah pertama yang tidak ter-cache. Kalau perubahan satu baris membatalkan install, urutan copy Anda yang salah dan tidak ada cache backend yang bisa menyelamatkan.
3. Tambahkan file yang seharusnya dibuang dockerignore, build ulang, dan pastikan tidak ada yang bergeser. Kalau ada yang bergeser, ada lubang di file ignore Anda.
4. Pakai progress plain ketika output ringkas bawaan menyembunyikan langkah yang Anda butuhkan; mode itu menampilkan output container, bukan ringkasan rapi.

Alasan untuk mengukur alih-alih menalar adalah karena cache itu sebuah hash, dan hash tidak punya intuisi. Saya pernah sangat yakin satu layer akan dipakai ulang dan ternyata salah, dua kali, dengan sebab yang sama: ada sesuatu yang saya lupa masih duduk di build context.

Kecepatan deploy di platform bukan fitur yang Anda beli, melainkan sifat dari Dockerfile yang Anda push. Letakkan langkah mahal di atas langkah yang gampang berubah, pangkas context sampai tinggal yang benar-benar dibaca build, pakai cache mount supaya install yang tak terhindarkan jadi murah, dan ekspor registry cache agar builder dingin berikutnya berangkat dari build terakhir Anda. Lalu buktikan di log, karena satu-satunya pendapat yang berlaku adalah kata CACHED.

## Sumber

- Docker Docs — Docker build cache: bagaimana pembatalan layer merambat di dalam Dockerfile
- Docker Docs — Optimize cache usage in builds: urutan layer, build context, dan cache mount
- Docker Docs — Dockerfile reference: cache mount pada RUN, opsinya, dan mode sharing
- Docker Docs — Registry cache backend: cache-to, cache-from, serta mode min dan max
- Docker Docs — Build garbage collection: policy bawaan dan apa yang dihitung ephemeral

## Source links

- https://docs.docker.com/build/cache/
- https://docs.docker.com/build/cache/optimize/
- https://docs.docker.com/reference/dockerfile/
- https://docs.docker.com/build/cache/backends/registry/
- https://docs.docker.com/build/cache/garbage-collection/

## FAQ

### Kenapa Docker build cepat di lokal tetapi lambat di PaaS?

Laptop Anda masih menyimpan layer dari build sebelumnya di builder lokal, jadi sebagian besar instruksi dipakai ulang. Builder PaaS atau CI dibuat untuk deploy lalu dihapus, sehingga layer cache-nya kosong dan semua instruksi dijalankan. Solusinya adalah mengekspor cache ke registry dengan cache-to dan mengimpornya dengan cache-from, supaya builder baru berangkat dari build terakhir Anda.

### Di mana COPY sebaiknya diletakkan agar cache hit paling tinggi?

Salin manifest dependency dan lockfile lebih dulu, jalankan install, baru salin sisa source. Panduan Docker sendiri menyarankan langkah mahal diletakkan di bagian awal, karena satu perubahan memaksa rebuild pada semua langkah sesudahnya. Dengan urutan itu, mengubah satu komponen tidak lagi membatalkan layer install.

### Apakah cache mount BuildKit bertahan antar deploy?

Tidak dengan sendirinya. Cache mount adalah direktori di builder, bukan layer dari image, jadi ia tidak ikut ter-push bersama image dan tidak diekspor oleh cache-to. Garbage collection bawaan BuildKit juga mendaftarkan exec.cachemount sebagai record ephemeral yang dibersihkan. Di builder ephemeral ia membantu di dalam satu build, bukan antar deploy.

### Apa beda mode=min dan mode=max pada cache-to?

min adalah default dan hanya menyimpan layer yang diekspor ke image hasil akhir. max menyimpan semua layer, termasuk layer dari langkah antara. Untuk multi-stage build, max biasanya yang Anda butuhkan, karena install dependency yang mahal justru berada di stage yang tidak pernah ikut dikirim.

### Bagaimana cara tahu layer Dockerfile mana yang merusak cache?

Baca build output dan cari langkah pertama yang tidak dicetak dengan tanda CACHED di depannya. Input langkah itulah yang berubah, dan semua yang di bawahnya memang akan dijalankan ulang. Ubah satu hal dalam satu waktu lalu build ulang, dan pakai progress plain ketika output ringkas menyembunyikan langkah yang perlu Anda lihat.

---

- HTML version: https://www.matthewswong.com/id/blog/docker-build-cache-paas-deploy-speed/
- English: https://www.matthewswong.com/en/blog/docker-build-cache-paas-deploy-speed/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
