Menit CI adalah salah satu biaya yang merayap naik. Pipeline yang dulu empat menit saat repo masih muda menjadi sebelas menit dua tahun kemudian, tidak ada yang ingat kapan itu terjadi, dan tiba-tiba setiap siklus review pull request menyertakan jeda ngopi. Saat saya mengaudit workflow di proyek klien dan aplikasi saya sendiri, tuas terbesar bukan paralelisme atau runner lebih besar — melainkan caching yang dilakukan dengan sengaja, bukan sekadar ikut-ikutan.
Artikel ini membahas tiga lapis cache yang penting di pipeline GitHub Actions pada umumnya — dependency package manager, layer image Docker, dan build cache framework — lengkap dengan konfigurasi persis yang saya jalankan, limit platform yang diam-diam menyabotase setup naif, dan anti-pattern yang terus saya temukan di repo nyata. Sebagian besar tim bisa memangkas 30 sampai 60 persen wall time CI dengan kerja satu sore di sini.
Bagaimana Cache GitHub Sebenarnya Berperilaku
Sebelum mengoptimasi apa pun, pahami dulu aturan platformnya, karena aturan ini membentuk setiap keputusan strategi:
- Setiap repository mendapat 10 GB storage cache secara default. Lewat itu, GitHub mengusir cache tertua lebih dulu — cache Anda yang lambat-dibangun-tapi-jarang-berubah bisa terusir oleh cache yang cepat-tapi-terus-dibangun-ulang.
- Entri cache yang tidak diakses selama 7 hari akan dihapus. Workflow rilis yang hanya jalan mingguan praktis tidak pernah punya cache hangat kecuali ada hal lain yang menjaga key-nya tetap hidup.
- Urutan pencarian: kecocokan key persis dulu, lalu restore-keys dari atas ke bawah sebagai prefix match. Hit di restore-keys tetap dihitung miss untuk key utama, jadi cache baru akan disimpan di akhir job.
- Cache yang dibuat di sebuah branch terlihat oleh branch itu dan turunannya, plus cache dari default branch terlihat di mana-mana. Hangatkan cache di main dan semua feature branch ikut menikmatinya.
Lapis 1: Caching Dependency, 80 Persen yang Murah
Untuk proyek Node, caching bawaan setup-node mencakup sebagian besar kebutuhan dengan satu baris — ia menghash lockfile Anda dan meng-cache store global package manager. Pakai pendekatan manual actions/cache hanya saat Anda meng-cache lebih dari dependency, misalnya cache build inkremental Next.js:
# 90% of Node projects only need this
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "npm" # also accepts yarn / pnpm
# manual control when you cache more than the package manager
- uses: actions/cache@v4
with:
path: |
~/.npm
.next/cache
key: nextjs-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
restore-keys: |
nextjs-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-
nextjs-${{ runner.os }}-Fallback restore-keys dua tingkat adalah bagian yang paling sering dilewati orang. Dengannya, perubahan lockfile tetap me-restore cache sebelumnya dan hanya mengunduh delta-nya; tanpanya, setiap bump dependency menjadi npm install dingin. Di aplikasi Next.js ukuran menengah ini bedanya antara step install 40 detik dan 3 menit.
Ukur sebelum dan sesudah: step cache mencatat hit/miss dan key yang di-restore. Saya menambah job summary satu baris yang mencetak status cache, karena rentetan partial miss yang senyap terlihat identik dengan caching yang bekerja kecuali Anda memeriksanya.
Lapis 2: Caching Layer Docker dengan BuildKit
Kalau workflow Anda membangun image, lapis ini mengerdilkan semua lapis lain. Secara default, setiap build CI mulai dari nol — tidak ada layer cache di runner yang masih segar. Backend cache GitHub Actions milik BuildKit memperbaikinya dengan dua baris:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/you/app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=maxBackend type=gha menyimpan blob layer di layanan cache yang sama dengan dependency Anda, artinya mereka berbagi budget 10 GB. Flag mode=max meng-cache layer perantara dari setiap build stage, bukan hanya image final — inilah yang membuat multi-stage build jadi cepat. Untuk image lebih besar ada opsi kedua yang layak diketahui:
Cache type=gha
Nol infrastruktur, ideal untuk image aplikasi di bawah kira-kira satu gigabyte layer. Terhitung dalam budget 10 GB repo dan eviction 7 hari. Butuh Buildx 0.21+ dan BuildKit 0.20+ karena layanan cache pindah ke API v2 pada April 2025.
Cache type=registry
Mendorong cache sebagai artefak ke container registry Anda dengan dukungan mode=max. Tidak dibatasi kuota Actions, selamat dari aturan 7 hari, bisa dibagi antar repo bahkan ke build lokal. Pilihan saya untuk base image dan apa pun yang dibangun ulang kurang dari harian.
Limit yang Diam-Diam Merusak Caching Anda
Tiga angka ini menjelaskan hampir semua pipeline misterius-lambat yang pernah saya debug:
| Limit | Nilai | Dampaknya bagi Anda |
|---|---|---|
| Budget cache repo | 10 GB default | Cache layer Docker dengan mode=max bisa mengusir cache dependency Anda. Pantau daftar cache di tab Actions. |
| Eviction karena tidak aktif | 7 hari | Workflow mingguan dan khusus rilis selamanya jalan dingin. Pertimbangkan job pemanas terjadwal atau cache berbasis registry. |
| Panjang key | 512 karakter | Ekspresi hashFiles panjang dengan banyak glob bisa meluber; hash satu file manifest hasil generate saja. |
Jangan pernah meng-cache node_modules langsung dengan key dari lockfile. Itu rusak antar versi Node dan image OS, menutupi script postinstall, dan hematnya lebih kecil daripada meng-cache store npm. Direktori cache milik package manager plus clean install itu lebih cepat sekaligus benar.
Anti-Pattern yang Terus Saya Temukan di Repo Nyata
- Satu cache raksasa untuk segalanya. Satu key untuk node_modules, binary Cypress, dan output build berarti perubahan apa pun menginvalidasi semuanya. Pisahkan cache berdasarkan frekuensi perubahan.
- Caching tanpa restore-keys. Pola persis-atau-tidak-sama-sekali mengubah setiap bump lockfile menjadi cold start penuh, padahal justru saat itulah Anda butuh fallback hangat.
- Key statis seperti deps-v1 yang tidak pernah menyertakan hash. Cache tidak pernah terinvalidasi, build berjalan dengan dependency basi, dan bug pertama yang sulit direproduksi harganya melebihi semua menit yang dihemat.
- Meng-cache paket apt atau toolchain yang sudah disediakan setup action dalam hitungan detik. Unduhan dari cache bisa lebih lambat daripada install yang Anda hindari — selalu bandingkan dengan baseline tanpa cache.
Audit Caching 30 Menit untuk Pipeline Anda
Ini urutan yang saya jalankan di setiap repo yang saya ambil alih:
- Buka sepuluh run terakhir dan catat wall time per step. Apa pun yang mengunduh atau mengompilasi adalah kandidat caching; urutkan berdasarkan total menit yang dikonsumsi, bukan waktu per run.
- Periksa daftar cache di Actions: ukuran total terhadap budget 10 GB, dan apakah key bernilai tinggi terusir oleh key yang sering berubah.
- Tambah atau perbaiki caching dependency dengan setup-node (atau actions/cache plus restore-keys), dan verifikasi hit pada run kedua.
- Tambahkan layer caching type=gha ke build Docker dengan mode=max; pindahkan image base atau bersama ke type=registry kalau eviction menggigit.
- Ukur ulang setelah seminggu traffic nyata, bukan run sintetis — perilaku cache di bawah PR yang berbarengan adalah kebenaran, run demo bukan.
Intinya
Di repo yang memicu audit ini, total waktu pipeline turun dari 11 menit menjadi sedikit di atas 4: caching dependency dengan restore-keys yang benar menghemat 2 menit, caching layer Docker dengan type=gha menghemat 4, dan cache build Next.js menghemat sisanya. Tidak ada yang butuh infrastruktur baru atau runner berbayar — cukup memahami aturan eviction dan menaruh tipe cache yang tepat di tiap lapis.
Caching adalah salah satu optimasi langka di mana kerjanya terbatas tapi hasilnya berbunga di setiap push. Luangkan satu sore itu. Reviewer Anda, frekuensi deploy Anda, dan tagihan Actions Anda membaik bersamaan.
Sumber dan bacaan lanjutan

