Yazılarımız

Veri Akademi

DOCKER IMAGE LAYERİNG VE CACHE: BUİLD SÜRELERİNİ KISALTMA TEKNİKLERİ

Bir Docker build’inin “birden bire” 2 dakikadan 20 dakikaya çıkması çoğu zaman gizem değildir; çoğunlukla katman sıralaması, kopyalama stratejisi ve cache’in boşa düşmesiyle ilgilidir. İyi haber: Docker image layering ve cache mantığını doğru anladığınızda build süreleri dramatik biçimde kısalır, CI maliyetleri düşer ve ekiplerin teslim hızı artar.

Bu makalede Docker imaj katmanlarının nasıl oluştuğunu, layer cache’in hangi koşullarda korunduğunu ve cache’i bilinçli yönetmek için pratik teknikleri ele alacağız. Hedef, tek bir “hızlandırma tüyosu” değil; tekrar üretilebilir, ölçülebilir ve ekipçe sürdürülebilir bir build yaklaşımı kurmaktır.

Okurken kendi Dockerfile’ınızı zihninizde canlandırın: Hangi adımlar sık değişiyor, hangi adımlar nadiren değişiyor, bağımlılıklar nerede kuruluyor ve kaynak kod ne zaman kopyalanıyor? Bu soruların cevapları, cache verimliliğinin doğrudan belirleyicisidir.

Docker imaj katmanlama mantığı: Neyi hızlandırıyoruz?

Docker imajı, Dockerfile’daki her talimatın (özellikle RUN, COPY, ADD) oluşturduğu katmanların üst üste gelmesiyle oluşur. Her katman, bir önceki katmana göre bir fark (delta) taşır. Bu yapı, layer cache sayesinde “değişmeyen” adımların tekrar çalıştırılmadan yeniden kullanılabilmesini sağlar.

Cache’in ana prensibi basittir: Docker, her adım için bir “anahtar” hesaplar. Bu anahtar; komutun kendisine, önceki katmanın kimliğine ve ilgili girdilere bağlıdır. Girdiler değişirse anahtar değişir, katman yeniden üretilir. Buradaki kritik nokta şudur: Cache, hız için bir yan ürün değil; doğru kurgulanması gereken bir tasarım kararıdır.

Katman sınırları neden önemlidir?

Katman sınırları, değişkenliğin izole edilmesini sağlar. Örneğin bağımlılık kurulumunu, sık değişen kaynak koddaki değişikliklerden ayırırsanız cache “vurur” ve bağımlılıklar yeniden kurulmaz. Tersi durumda ise küçük bir dosya değişimi bile tüm sonraki adımları boşa düşürür.

Hangi talimatlar cache’i daha sık bozar?

COPY/ADD ile kopyalanan dosyaların değişmesi, ilgili adımın cache’ini kırar ve sonraki tüm katmanlar yeniden üretilir. Ayrıca RUN içindeki “zamana bağlı” işlemler (ör. her build’de paket listesi çekmek) deterministik değilse cache verimini düşürür.


Layer cache nasıl çalışır: Cache hit, cache miss ve invalidation

Docker, her adım için önceki katman + komut + dosya içerikleri (kontekst) üzerinden cache eşleşmesi arar. Eşleşme bulunursa cache hit olur ve adım çalıştırılmadan katman yeniden kullanılır. Eşleşme yoksa cache miss olur, adım tekrar çalışır ve yeni bir katman üretir.

Cache invalidation zincir reaksiyonu yaratır: Erken bir adım bozulursa, onun üstündeki tüm adımların anahtarı değişir. Bu yüzden Dockerfile sıralaması, build hızının “grafiğini” belirler.

Deterministik build yaklaşımı

Cache’in güvenilir olması için adımların mümkün olduğunca deterministik olması gerekir. Örneğin paket yöneticisi kullanıyorsanız, lock dosyalarıyla sürüm sabitlemek ve ayna/registry kaynaklarını tutarlı seçmek önemlidir. Böylece aynı girdilerle aynı sonuç üretilir; cache daha anlamlı hale gelir.

Build context ve gereksiz değişiklikler

Docker build context’ine giren her dosya, cache anahtarlarını etkileyebilir. Bir log dosyasının ya da local ayar dosyasının konteksle taşınması, fark etmeden cache miss’e yol açabilir. Bu noktada .dockerignore kritik bir rol oynar.

  • Sık değişen dosyaları (log, test çıktılarını) konteks dışına alın.
  • Node/Python gibi ekosistemlerde yerel cache klasörlerini ignore edin.
  • Derleme çıktılarının konteksle gitmesini engelleyip “temiz” bir kaynak seti gönderin.

Dockerfile optimizasyonu: Sıralama, kopyalama stratejisi ve küçük ama etkili hamleler

Dockerfile optimizasyonunun omurgası şudur: Nadiren değişen adımları yukarı taşıyın, sık değişen adımları aşağı bırakın. Bağımlılık kurulumu genellikle nadiren değişir; uygulama kodu sık değişir. Bu ikisini aynı katmanda buluşturmak cache’i cezalandırır.

Bağımlılıkları koddan önce konumlandırma

Örneğin bir Node.js projesinde önce package.json ve package-lock.json kopyalanır, bağımlılıklar kurulur; ardından kaynak kod eklenir. Böylece kod değişse bile bağımlılık katmanı cache’lenebilir.

Tekrar eden paket indeks güncellemelerini azaltma

Linux tabanlı imajlarda apt-get update gibi adımlar, doğru birleştirilmezse hem gereksiz katman üretir hem de cache davranışını zayıflatır. Paket kurulumlarını tek bir RUN içinde birleştirmek, katman sayısını azaltır ve daha tutarlı build sağlar.

Örnek: Cache dostu bir Dockerfile

FROM node:20-alpine AS base
WORKDIR /app

# 1) Önce bağımlılık manifestlerini kopyala
COPY package.json package-lock.json ./

# 2) Bağımlılıkları kur (sık değişmeyen katman)
RUN npm ci --omit=dev

# 3) Sonra uygulama kodunu kopyala (sık değişen katman)
COPY . .

# 4) Uygulama build/çalıştırma adımı
EXPOSE 3000
CMD ["node", "server.js"]

Bu yapı, küçük bir kaynak kod değişikliğinde yalnızca COPY . . ve sonrasını etkiler. Bağımlılık kurulumu çoğu durumda cache hit alır. Ayrıca, konuya derinleşmek isterseniz Docker Eğitimi içeriğinde benzer senaryoları farklı dil ve platformlarda pratik ediyoruz.

Katmanlı Docker build sürecinde cache vuruşlarının hız kazandıran akışı

BuildKit ile gelişmiş cache: Inline cache, cache mount ve paralel build

Modern Docker build dünyasında BuildKit önemli bir sıçrama sağlar. BuildKit; daha iyi önbellekleme, paralel yürütme ve gelişmiş özelliklerle cache verimini artırır. Özellikle CI ortamında build süreleri dalgalanıyorsa, BuildKit’i etkinleştirmek “tutarlılık” açısından da anlamlıdır.

BuildKit’i etkinleştirme ve ölçüm

Local geliştirmede BuildKit genellikle varsayılan hale geldi, ancak bazı ortamlarda explicit etkinleştirme gerekebilir. En kritik adım: değişiklikten önce/sonra süreyi ölçmek ve cache hit oranını gözlemlemektir.

# BuildKit ile build almak
DOCKER_BUILDKIT=1 docker build -t myapp:latest .

# Buildx ile daha gelişmiş senaryolar (ör. registry cache)
docker buildx build --progress=plain -t myapp:latest .

Cache mount: Paket yöneticisi cache’ini değerlendirme

BuildKit ile --mount=type=cache yaklaşımı, paket yöneticilerinin indirme cache’ini koruyarak özellikle CI tekrarlarında ciddi hız kazandırabilir. Bu yöntem, her seferinde aynı paketleri yeniden indirmek yerine paylaşımlı bir cache alanı kullanır. Burada dikkat edilmesi gereken, cache’in kapsamı ve temizliğiyle ilgili kuralları ekip standardına bağlamaktır.

Inline cache ve uzaktan cache paylaşımı

Inline cache, build çıktısının metadata’sına cache bilgisini gömerek registry üzerinden yeniden kullanılabilmesini sağlar. Böylece tek bir CI runner’a bağlı kalmadan, farklı runner’lar arasında cache paylaşımı mümkün olur. Bu yaklaşım, dağıtık ekipler ve çoklu pipeline senaryolarında özellikle etkilidir.


Multi-stage build: Hem hızlı build hem küçük imaj

Multi-stage build yaklaşımı, build araçlarıyla runtime bağımlılıklarını ayırır. İlk aşamada derleme yapılır, ikinci aşamada yalnızca çalışmak için gereken çıktılar taşınır. Sonuç: daha küçük imaj, daha az saldırı yüzeyi ve çoğu durumda daha hızlı “pull” süreleri.

Build ve runtime aşamalarını ayırma

Derleme için ağır bir base imaj kullanıp runtime’da minimal bir imajla çalışmak mümkündür. Bu ayrım, cache açısından da avantaj sağlar: Derleme aşamasındaki bağımlılıklar stabilse, bu aşama sıklıkla cache hit alır.

Hedef aşama kullanımı

Docker build sırasında belirli bir aşamaya kadar build almak, özellikle geliştirme sırasında faydalıdır. Böylece gereksiz adımlar çalıştırılmadan hızlı geri bildirim sağlanır. Ayrıca CI’da farklı job’lar için farklı hedef aşamalar seçilerek pipeline süreleri kısaltılabilir.

  1. Bağımlılıkları kuran aşamayı sabitleyin.
  2. Derleme çıktısını üreten aşamayı izole edin.
  3. Runtime aşamasında yalnızca gerekli artefact’ları kopyalayın.

CI/CD’de cache stratejileri: Registry cache, katman paylaşımı ve pratik tuzaklar

Yerelde cache hit alıp CI’da cache miss almak çok yaygın bir problemdir. Bunun nedeni, CI runner’ların stateless olması veya cache’in uygun şekilde taşınmamasıdır. CI tarafında amaç, katmanları ya runner içinde kalıcı tutmak ya da registry üzerinden paylaşmaktır.

Registry tabanlı cache yaklaşımı

Katmanları bir registry’de saklamak, farklı runner’larda bile cache’den faydalanmayı sağlar. Buildx ile “cache-to / cache-from” yaklaşımıyla cache push/pull yapılabilir. Bu yaklaşımın faydası, özellikle monorepo ve çoklu servislerde belirginleşir; çünkü benzer bağımlılık katmanları servisler arasında da ortaklaşabilir.

Cache’i gereksiz yere bozan pratikler

CI tarafında sık görülen hatalar şunlardır: tüm repo’yu erken aşamada kopyalamak, konteks içine gereksiz dosyalar almak, her build’de bağımlılık kurulumunu “temiz” başlatmak ve build argümanlarını kontrolsüz değiştirmek. Bir diğer kritik konu da “build secret” kullanımıdır: Secret’ların yanlış yerde kullanılması, cache’in beklenmedik biçimde devre dışı kalmasına neden olabilir.

Pipeline için önerilen kontrol listesi

  • Cache anahtarlarını etkileyen ARG/ENV değerlerini standardize edin.
  • Lock dosyalarını ve bağımlılık adımlarını erken katmanlarda konumlandırın.
  • .dockerignore ile konteksi küçültün ve gereksiz değişiklikleri engelleyin.
  • Build log’larında cache hit/miss davranışını düzenli takip edin.
  • Registry cache ile runner bağımlılığını azaltın.
BuildKit etkinleştirilmiş pipeline’da hedef aşamaya kadar yeniden kullanım örneği ve ölçüm

Gözlemleme ve troubleshooting: Neden yavaşladı, nerede bozuldu?

Build süreleri arttığında ilk tepki çoğu zaman “CI bozuldu” olur. Oysa pratikte, tek bir satır değişikliği cache zincirini etkiliyor olabilir. Bu nedenle ölçüm odaklı ilerlemek önemlidir. Build çıktısında hangi adımın yeniden çalıştığını, hangi adımın cache aldığını izlemek, sorunu dakikalar içinde görünür kılar.

Cache zincirini okumak

--progress=plain çıktısı, hangi adımın cache hit aldığını daha net gösterir. Adımların beklenmedik şekilde yeniden çalıştığını görürseniz, ilgili adımın girdilerini kontrol edin: Kopyalanan dosyalar mı değişti, komut mu değişti, önceki katman mı bozuldu?

Yapısal iyileştirme yaklaşımı

Dockerfile’ı sadece “çalışıyor” diye bırakmak, uzun vadede ekip hızını düşürür. İyi bir yaklaşım, Dockerfile’ı yaşayan bir yapı olarak ele almak ve belirli aralıklarla iyileştirmektir. Özellikle bağımlılık kurulum süreleri, güvenlik güncellemeleri ve base imaj değişimleri bu iyileştirme döngüsünün doğal tetikleyicileridir.

Özetle: Docker image layering ve cache’i doğru kurgulamak, build sürelerini kısaltmanın en etkili yoludur. Katman sıralaması, BuildKit özellikleri, multi-stage yaklaşımı ve CI cache stratejileri birlikte ele alındığında hem hız hem de tutarlılık kazanırsınız. Bu optimizasyonları bir kez oturttuğunuzda, her yeni commit’te “yeniden derleme çilesi” yerine hızlı ve güvenilir bir build akışı elde edersiniz.

 VERİ AKADEMİ