Adityacprtm

Aditya Pratama

DevO
  • EmailEmail
  • Location Jakarta, ID
Return to Blog

GitHub Actions Docker Cache: gha vs registry vs inline

April 17, 2026 · 8 min read

Perbandingan Docker cache GitHub Actions: gha, registry, dan inline. Cek benchmark, trade-off, dan rekomendasi strategi optimasi build.

Adityacprtm

Salah satu keluhan paling umum soal CI/CD pipeline adalah: build Docker-nya lama. Dan salah satu cara paling efektif untuk mengatasinya adalah caching yang tepat.

GitHub Actions + BuildKit punya tiga strategi cache yang sering dipakai: type=gha, type=registry, dan type=inline. Ketiganya tersedia, ketiganya punya trade-off, dan pilihan yang salah bisa bikin build tetap lambat — atau malah lebih lambat dari sebelumnya.

Post ini dokumentasi eksplorasi dan benchmark yang aku lakukan di pipeline internal. Hasilnya mungkin tidak akan persis sama di environment kamu, tapi polanya cukup konsisten.


Setup yang Digunakan

Sebelum masuk ke perbandingan, ini konteks setup yang dipakai:

  • Runner: GitHub-hosted ubuntu-latest
  • Image: Multi-stage build — base Node.js (dependencies) → build stage → production Nginx
  • Ukuran image final: ~85 MB
  • Frekuensi push: ~15–20x per hari (aktif development)
  • BuildKit: enabled via docker/setup-buildx-action

Setup dasar workflow-nya seperti ini:

yamlCopy
- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Build and push
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: ${{ env.IMAGE_TAG }}
    cache-from: # <- ini yang akan berubah per strategi
    cache-to: # <- ini juga

Tiga Strategi Cache

1. type=gha — GitHub Actions Cache

Cache disimpan di GitHub Actions Cache storage (bukan registry). Terintegrasi langsung dengan ekosistem GitHub.

yamlCopy
cache-from: type=gha
cache-to: type=gha,mode=max

Cara kerjanya: BuildKit menyimpan cache layers ke GitHub Actions Cache API. Cache ini scoped per branch dan per workflow, mirip cara kerja actions/cache.

Kelebihan:

  • Setup paling simpel — zero config tambahan
  • Tidak butuh credentials registry
  • Cache otomatis expired sesuai retention policy GitHub (7 hari untuk cache yang tidak diakses)
  • Gratis selama masih dalam quota storage GitHub Actions

Kekurangan:

  • Cache storage limit: 10 GB per repository (shared dengan semua workflow lain)
  • Tidak bisa di-share antar repository
  • Cache miss total kalau runner baru atau branch baru tanpa history
  • Upload/download speed bergantung pada GitHub infrastructure

2. type=registry — Registry Cache

Cache disimpan sebagai layer di container registry (Docker Hub, ECR, GCR, dll.) — terpisah dari image production.

yamlCopy
cache-from: type=registry,ref=xxxx.dkr.ecr.ap-southeast-1.amazonaws.com/myapp:cache
cache-to: type=registry,ref=xxxx.dkr.ecr.ap-southeast-1.amazonaws.com/myapp:cache,mode=max

Cara kerjanya: BuildKit push cache layers ke tag khusus di registry (biasanya :cache atau :buildcache). Setiap build pull layer yang sudah ada, dan push layer baru yang berubah.

Kelebihan:

  • Persistent — tidak ada expiry seperti GitHub Cache
  • Bisa di-share antar repository dan antar workflow
  • Cache tersedia selama registry masih aktif
  • Cocok untuk monorepo atau multiple pipeline yang share base image

Kekurangan:

  • Butuh registry credentials (setup lebih banyak)
  • Kalau pakai ECR: ada biaya storage dan transfer
  • Cold start lambat — pull cache dari registry lebih lambat dari GitHub Cache
  • Perlu manajemen manual kalau cache tumbuh besar

3. type=inline — Inline Cache

Cache metadata disimpan di dalam image itu sendiri, bukan di tempat terpisah.

yamlCopy
cache-from: type=registry,ref=xxxx.dkr.ecr.ap-southeast-1.amazonaws.com/myapp:latest
cache-to: type=inline

Cara kerjanya: Cache disimpan sebagai metadata di dalam image yang di-push. Saat build berikutnya, BuildKit pull image latest dan pakai metadata itu sebagai cache hint.

Kelebihan:

  • Tidak butuh storage tambahan — cache ada di dalam image
  • Setup paling sederhana dari sisi infrastructure
  • Tidak ada overhead pull cache terpisah

Kekurangan:

  • Hanya support mode=min — artinya hanya layer final yang di-cache, bukan intermediate stages
  • Untuk multi-stage build, ini masalah besar — stage dependencies dan builder layers tidak ikut ter-cache
  • Efektivitasnya jauh di bawah dua metode lain untuk build yang kompleks

Benchmark

Aku jalankan 30 builds per strategi selama 3 hari kerja, dengan variasi:

  • Cache hit: hanya perubahan di application code (bukan dependencies)
  • Cache miss: perubahan di package.json atau base image update
  • Cold start: branch baru, tidak ada cache sama sekali

Build Time (detik)

Kondisi Tanpa Cache inline gha registry
Cold start 187s 183s 181s 178s
Cache miss (deps berubah) 187s 179s 142s 138s
Cache hit (code only) 187s 164s 48s 52s
Cache hit rata-rata 187s 158s 51s 55s

Catatan: Angka di atas adalah rata-rata dari 30 run. Variasi antar run bisa ±10–15 detik tergantung load GitHub runner.

Penghematan Waktu per Hari

Dengan ~18 build/hari dan mayoritas adalah cache hit (code-only changes):

Strategi Build time/hari Hemat vs tanpa cache
Tanpa cache ~56 menit
inline ~47 menit ~9 menit
gha ~15 menit ~41 menit
registry ~17 menit ~39 menit

Analisis: Kenapa inline Kalah Jauh?

Ini yang paling menarik dari benchmark ini. Untuk multi-stage build, inline hampir tidak memberikan manfaat signifikan di cache hit scenario.

Penyebabnya: inline hanya menyimpan cache untuk final stage. Sedangkan di multi-stage build, stage yang paling “berat” biasanya ada di tengah — install dependencies, compile, dll.

Ilustrasinya:

dockerfileCopy
# Stage 1: deps — paling berat, ~90s
FROM node:20-alpine AS deps
COPY package*.json ./
RUN npm ci  # <-- inline cache TIDAK cover ini secara efektif

# Stage 2: build — medium, ~40s
FROM deps AS builder
COPY . .
RUN npm run build

# Stage 3: production — ringan, ~5s
FROM nginx:alpine AS runner  # <-- inline cache hanya cover sampai sini
COPY --from=builder /app/dist /usr/share/nginx/html

Dengan gha atau registry dan mode=max, semua stage ter-cache. Jadi kalau hanya kode yang berubah (bukan package.json), stage deps di-skip sepenuhnya.


gha vs registry: Mana yang Lebih Worth?

Dari benchmark, selisihnya tipis — gha sedikit lebih cepat di cache hit karena GitHub Cache biasanya lebih dekat dengan runner. Tapi keputusannya bukan soal speed semata.

Faktor gha registry
Setup complexity Rendah Medium
Persistensi cache 7 hari (tidak diakses) Permanent
Share antar repo
Biaya Gratis (dalam quota) Storage + transfer
Cocok untuk Single repo, tim kecil Monorepo, shared base image
Cache size limit 10 GB/repo Tergantung registry plan
Cold start performance ≈ sama ≈ sama

Kesimpulan praktis:

  • Kalau repository independent dan tidak ada shared base image → gha sudah cukup
  • Kalau ada multiple pipeline yang share layer, atau butuh cache yang tidak expired → registry
  • inline hanya masuk akal untuk single-stage build yang sangat sederhana

Konfigurasi Lengkap yang Aku Pakai

Ini final config yang aku pakai setelah benchmarking, menggunakan gha dengan mode=max:

yamlCopy
name: Build and Push

on:
  push:
    branches: [main, develop]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-southeast-1

      - name: Login to ECR
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ secrets.ECR_REGISTRY }}/${{ env.APP_NAME }}:${{ github.sha }}
            ${{ secrets.ECR_REGISTRY }}/${{ env.APP_NAME }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            NODE_ENV=production

Satu hal yang perlu diperhatikan: kalau kamu punya banyak workflow di repo yang sama, 10 GB cache limit bisa cepat penuh. Pantau usage-nya di Settings → Actions → Caches.


Tips Tambahan

Gunakan mode=max selalu untuk multi-stage build. mode=min (default) hanya cache output final stage. Untuk multi-stage, ini sama buruknya dengan inline.

Pisahkan package.json COPY dari source code COPY. Ini bukan tips cache type, tapi paling impactful:

dockerfileCopy
# ✅ Benar — dependencies di-cache terpisah
COPY package*.json ./
RUN npm ci
COPY . .  # perubahan di sini tidak invalidate layer npm ci

# ❌ Salah — setiap perubahan file apapun invalidate npm ci
COPY . .
RUN npm ci

Jangan cache layer yang mengandung secrets. Kalau ada ARG atau ENV yang berisi credentials di tengah build, layer itu sebaiknya tidak masuk cache. Letakkan credentials-related instructions di akhir, setelah semua layer yang cacheable.


Penutup

Tidak ada strategi cache yang universally terbaik — semuanya bergantung pada struktur build dan workflow. Tapi dari pengalaman ini, urutan rekomendasi untuk kebanyakan use case adalah:

  1. gha dengan mode=max — paling mudah, efektif untuk single-repo pipeline
  2. registry dengan mode=max — kalau butuh persistensi atau sharing antar repo
  3. inline — hindari untuk multi-stage build

Yang paling penting: apapun strateginya, pastikan Dockerfile-mu sudah dioptimasi untuk layer ordering. Cache terbaik tidak akan banyak membantu kalau layer ordering salah.


Referensi:

That’s it.

Explored Topics
Comments
← PreviousStop Bundling Assets di Docker Image: Static Assets dengan S3 + Cloudflare CDNNext →Dari Alibaba Cloud ke AWS: Catatan Migrasi dengan AWS MGN
© 2026 Aditya Chamim Pratama