Docker & DevOps

    BuildKit ve Katman Önbelleği ile Hızlı Build

    Docker derlemesini dakikalardan saniyelere indiren önbellek stratejileri ve BuildKit özellikleri.

    9 dk okuma Güncellendi: 25 Ağustos 2026

    Tek satırlık bir kod değişikliği yaptın, docker build çalıştırdın ve dört dakika bekledin. Bağımlılıklar baştan indirildi, derleyici baştan koştu, hiçbir şey değişmemiş olan adımlar sanki ilk kez çalışıyormuş gibi tekrarlandı. BuildKit ve katman önbelleği doğru kurulduğunda bu süre çoğu projede 10-20 saniyeye iner ve fark, günde onlarca derleme yapan bir ekip için doğrudan zamandır.

    Bu rehberde önbelleğin hangi kurala göre geçersizleştiğini, COPY sırasını neden bağımlılık dosyalarıyla başlatman gerektiğini, BuildKit'in klasik derleyiciden farkını, RUN --mount=type=cache ile paket indirmelerini nasıl kalıcı hâle getireceğini, çok aşamalı derlemede paralelliği nasıl kullanacağını ve CI koşucuları gibi her seferinde sıfırdan başlayan ortamlarda önbelleği nasıl taşıyacağını anlatacağım. Dockerfile temellerini gözden geçirmek istersen Dockerfile en iyi pratikler yazısı bu rehberin doğal ön adımı.

    Katman Önbelleği Hangi Kurala Göre Çalışır#

    Bir Dockerfile'daki her komut yeni bir katman üretir ve Docker her katman için bir önbellek anahtarı hesaplar. Bu anahtar iki şeyden oluşur: üst katmanın kimliği ve o adımın kendisi. RUN, ENV, WORKDIR gibi komutlarda "adımın kendisi" komut metninin ta kendisidir; tek bir boşluk bile değişse önbellek ıskalar. COPY ve ADD komutlarında ise metin değil, kopyalanan dosyaların içeriğinden hesaplanan özet kullanılır.

    Buradan çıkan ve her şeyi belirleyen kural şu: bir katmanın önbelleği ıskaladığı anda, ondan sonraki bütün katmanlar da ıskalar. Zincir kırıldıktan sonra geri dönüş yoktur. Bu yüzden Dockerfile'ı yazarken sıralamayı "en az değişenden en çok değişene" doğru kurmak, tek başına en yüksek etkili optimizasyondur.

    Klasik hatayı ve düzeltilmiş hâlini yan yana koyalım:

    # YANLIŞ: kaynak kod bağımlılıklardan önce kopyalanıyor
    FROM node:20-slim
    WORKDIR /app
    COPY . .
    RUN npm ci          # tek satır kod değişse bile baştan çalışır
    CMD ["node", "server.js"]
    
    # DOĞRU: önce kilit dosyaları, sonra kod
    FROM node:20-slim
    WORKDIR /app
    
    # Bu iki dosya nadiren değişir; npm ci katmanı önbellekte kalır
    COPY package.json package-lock.json ./
    RUN npm ci --omit=dev
    
    # Kod sık değişir; yalnızca bu ve sonrası yeniden çalışır
    COPY . .
    CMD ["node", "server.js"]
    

    Aynı mantık her ekosistemde geçerlidir: PHP'de composer.json ve composer.lock, Python'da requirements.txt ya da pyproject.toml, Go'da go.mod ve go.sum önce kopyalanır. Bu tek değişiklik, tipik bir projede tekrarlanan derleme süresini dakikalardan saniyelere indirir.

    Önbelleğin çalışıp çalışmadığını çıktıdan takip edebilirsin:

    docker build --progress=plain -t firmaniz/api:1.0 . 2>&1 | grep -E 'CACHED|DONE'
    # #7 [3/6] RUN npm ci --omit=dev
    # #7 CACHED
    

    CACHED görüyorsan o adım hiç çalışmadı demektir. .dockerignore dosyan eksikse COPY . . adımı neredeyse hiç önbelleğe girmez; sebebini .dockerignore kullanımı yazısında ayrıntılı anlattım.

    BuildKit Nedir, Klasik Derleyiciden Farkı Ne#

    BuildKit, Docker'ın yeni nesil derleme motorudur ve modern Docker sürümlerinde varsayılan olarak devrededir. Eski motorla arasındaki farklar kozmetik değil, mimaridir:

    ÖzellikKlasik derleyiciBuildKit
    Adım sırasıKesinlikle doğrusalBağımlılık grafiğine göre paralel
    Kullanılmayan aşamalarYine de derlenirHiç çalıştırılmaz
    Bağlam transferiTümü baştan gönderilirYalnızca gereken dosyalar
    Önbellek dışa aktarmaYokRegistry / dosya / CI önbelleği
    Gizli bilgi desteğiYok (katmana yazılır)--mount=type=secret
    Paket önbelleğiYok--mount=type=cache

    Devrede olduğunu doğrulamak ve gerekiyorsa açmak için:

    # Derleyici sürümünü gör
    docker buildx version
    
    # Eski bir Docker'da BuildKit'i açmak
    export DOCKER_BUILDKIT=1
    docker build -t firmaniz/api:1.0 .
    
    # Ya da kalıcı olarak /etc/docker/daemon.json içine:
    # { "features": { "buildkit": true } }
    

    BuildKit'in gelişmiş komutlarını (özellikle --mount) kullanabilmek için Dockerfile'ın ilk satırına bir söz dizimi yönergesi koyman gerekir:

    # syntax=docker/dockerfile:1
    FROM node:20-slim
    

    Bu satır olmadan RUN --mount=... yazarsan "unknown flag" hatası alırsın. Yorum gibi görünmesine aldanma; derleme motoru bu satırı bir yapılandırma direktifi olarak okur ve dosyanın en başında olmak zorundadır.

    RUN --mount=type=cache ile Paket İndirmelerini Kalıcı Kılmak#

    Katman önbelleği "adım hiç değişmediyse atla" mantığıyla çalışır. Ama package.json dosyasına tek bir bağımlılık eklediğinde o adım değişir ve npm ci baştan koşar — üstelik daha önce indirdiğin 400 paketi de yeniden indirir. cache bağlaması tam olarak bunu çözer: adım yeniden çalışsa bile indirme önbelleği diskte kalır.

    # syntax=docker/dockerfile:1
    FROM node:20-slim
    WORKDIR /app
    
    COPY package.json package-lock.json ./
    RUN --mount=type=cache,target=/root/.npm \
        npm ci --omit=dev
    
    COPY . .
    CMD ["node", "server.js"]
    

    Python ve Go tarafındaki karşılıkları:

    # Python: pip indirme önbelleği
    RUN --mount=type=cache,target=/root/.cache/pip \
        pip install --no-cache-dir -r requirements.txt
    
    # Go: modül ve derleme önbelleği birlikte
    RUN --mount=type=cache,target=/go/pkg/mod \
        --mount=type=cache,target=/root/.cache/go-build \
        go build -o /bin/uygulama ./cmd/api
    

    Buradaki kritik bilgi şu: cache bağlaması imajın parçası değildir. Bağlanan dizin yalnızca o RUN adımı sürerken görünür, sonuçtaki katmana yazılmaz. Yani imaj boyutunu artırmaz ve içindeki veriler imajı çeken kişiye gitmez. Aynı sebeple, önbelleğin kendisi de registry'ye aktarılmaz; derleme yapılan makinede yaşar.

    Aynı mekanizmanın gizli bilgi sürümü de vardır ve özel paket depolarına erişirken hayat kurtarır:

    # Token asla katmana yazılmaz, yalnızca bu adımda görünür
    RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
        npm ci --omit=dev
    
    docker build --secret id=npmrc,src=$HOME/.npmrc -t firmaniz/api:1.0 .
    

    --build-arg ile token geçirmenin aksine bu yöntemde değer docker history çıktısında görünmez. Sırların imaj katmanlarında birikmesinin ne kadar tehlikeli olduğunu .git ve .env dosyası ifşası yazısındaki örnekler iyi anlatıyor.

    Çok Aşamalı Derleme ve Paralellik#

    Çok aşamalı derleme genelde "imajı küçültme" tekniği olarak anlatılır ama BuildKit ile birlikte aynı zamanda bir hız tekniğidir. Birbirine bağlı olmayan aşamalar aynı anda çalışır:

    # syntax=docker/dockerfile:1
    
    # 1. aşama: ön yüz varlıkları
    FROM node:20-slim AS frontend
    WORKDIR /src
    COPY web/package*.json ./
    RUN --mount=type=cache,target=/root/.npm npm ci
    COPY web/ ./
    RUN npm run build
    
    # 2. aşama: arka uç ikilisi (frontend ile PARALEL çalışır)
    FROM golang:1.22 AS backend
    WORKDIR /src
    COPY go.mod go.sum ./
    RUN --mount=type=cache,target=/go/pkg/mod go mod download
    COPY . .
    RUN --mount=type=cache,target=/root/.cache/go-build \
        CGO_ENABLED=0 go build -o /bin/api ./cmd/api
    
    # 3. aşama: minik çalışma imajı
    FROM alpine:3.20
    RUN adduser -D -u 10001 uygulama
    COPY --from=backend /bin/api /usr/local/bin/api
    COPY --from=frontend /src/dist /var/www/html
    USER uygulama
    ENTRYPOINT ["/usr/local/bin/api"]
    

    Burada frontend ve backend aşamaları birbirine hiç bağlı olmadığı için BuildKit ikisini eş zamanlı yürütür; toplam süre en uzun aşamanın süresi kadar olur. Klasik derleyicide bu iki aşama sırayla koşardı.

    İkinci kazanç hedefli derlemedir. Yalnızca test aşamasını çalıştırmak istersen tüm dosyayı derlemene gerek yok:

    # Yalnızca "test" aşamasını çalıştır, sonrasını hiç derleme
    docker build --target test -t firmaniz/api:test .
    

    Son imajın boyutunu da düşürmek istiyorsan Docker imaj boyutu optimizasyonu yazısındaki teknikler bu yapıyla doğrudan birleşir.

    CI Tarafında Önbelleği Kalıcı Kılmak#

    Yerelde kurduğun önbellek CI'da işe yaramaz, çünkü her iş temiz bir koşucuda başlar ve yerel katman deposu boştur. Bu yüzden CI derlemeleri neredeyse her zaman tam süre koşar. Çözüm, önbelleği dışarı aktarıp bir sonraki koşuda geri almaktır.

    # Registry'ye önbellek yaz ve oradan oku
    docker buildx build \
      --cache-from type=registry,ref=firmaniz/api:buildcache \
      --cache-to   type=registry,ref=firmaniz/api:buildcache,mode=max \
      -t firmaniz/api:1.0 \
      --push .
    

    mode=max önemlidir: varsayılan min yalnızca son imaja giren katmanları dışa aktarır, max ise ara aşamaların katmanlarını da yazar. Çok aşamalı bir derlemede asıl kazanç ara aşamalarda olduğu için mode=max çoğu zaman doğru tercihtir; karşılığında önbellek deposu büyür.

    GitHub Actions kullanıyorsan koşucunun kendi önbellek servisini hedefleyebilirsin:

          - uses: docker/setup-buildx-action@v3
    
          - name: Derle ve gönder
            uses: docker/build-push-action@v6
            with:
              context: .
              push: true
              tags: firmaniz/api:${{ github.sha }}
              cache-from: type=gha
              cache-to: type=gha,mode=max
    

    Önbellek türlerini kısaca karşılaştıralım:

    TürNerede saklanırNe zaman uygun
    inlineİmajın kendi içindeBasit kurulum, tek aşamalı derleme
    registryAyrı bir imaj etiketindeÇok aşamalı, sağlayıcıdan bağımsız CI
    ghaGitHub Actions önbelleğiGitHub üzerinde çalışan hatlar
    localDiskteki bir dizindeKendi kendine barındırılan koşucular

    Bir uyarı: önbellek sınırsız büyür. Kendi derleme sunucunda çalışıyorsan düzenli temizlik yap, yoksa disk sessizce dolar:

    docker buildx du                       # önbellek kullanımını gör
    docker builder prune --filter until=168h   # bir haftadan eski girdileri sil
    

    Diskin zaten doluysa ve önce yer açman gerekiyorsa Docker diski doldurdu yazısındaki adımları uygula.

    Sık Yapılan Hatalar ve Tuzaklar#

    1. # syntax satırını unutmak. RUN --mount kullanacaksan bu satır dosyanın ilk satırı olmalıdır; yoksa bilinmeyen bayrak hatası alırsın.
    2. COPY . . satırını en başa koymak. Tek bir README değişikliği bile bütün bağımlılık kurulumunu geçersiz kılar. Kilit dosyalarını her zaman önce kopyala.
    3. apt-get update ile apt-get install komutlarını ayrı RUN satırlarına bölmek. update katmanı önbellekte kaldığı için aylar öncesinin paket listesiyle kurulum yaparsın; ikisi her zaman aynı RUN içinde olmalıdır.
    4. ARG değerlerini gereğinden erken tanımlamak. Bir ARG değeri değiştiğinde, o ARG'ın kullanıldığı yerden itibaren tüm katmanlar ıskalar. Sık değişen argümanları (sürüm numarası, commit kimliği) Dockerfile'ın sonuna yakın tanımla.
    5. cache bağlamasını imaj önbelleğiyle karıştırmak. --mount=type=cache adımın yeniden çalışmasını engellemez, yalnızca içindeki indirmeyi hızlandırır. İkisi birbirini tamamlar, birbirinin yerine geçmez.
    6. CI'da --no-cache bayrağını alışkanlıkla kullanmak. "Temiz derleme" hissi verir ama her koşuyu tam süreye çıkarır. Gerçekten gerektiğinde (temel imajı tazelemek gibi) haftalık zamanlanmış bir işte kullan, her commit'te değil.

    Sıkça Sorulan Sorular#

    BuildKit varsayılan olarak açık mı#

    Modern Docker sürümlerinde docker build komutu BuildKit üzerinden çalışır. Eski bir kurulumda veya BuildKit'in kapatıldığı bir ortamda DOCKER_BUILDKIT=1 ortam değişkeniyle açabilir ya da /etc/docker/daemon.json içinde kalıcı hâle getirebilirsin. Açık olup olmadığını, derleme çıktısının adım adım ilerleyen yeni biçimde görünmesinden ve docker buildx version komutunun cevap vermesinden anlarsın.

    Docker build neden her seferinde baştan çalışıyor#

    En yaygın üç sebep şunlar: COPY . . satırının bağımlılık kurulumundan önce gelmesi, .dockerignore dosyasının olmaması nedeniyle .git veya node_modules değişikliklerinin bağlamı sürekli değiştirmesi ve CI ortamında önbelleğin hiç taşınmaması. Üçünü de sırayla kontrol et; ilk ikisi yerel derlemede, üçüncüsü CI'da baskın sebeptir.

    Katman önbelleği ile cache mount arasındaki fark nedir#

    Katman önbelleği bir adımın hiç çalıştırılmamasını sağlar; girdi değişmediyse Docker o katmanı hazırdan kullanır. Cache mount ise adım çalıştığında kullanılan bir dizini kalıcı tutar; örneğin npm ci yeniden koşsa bile paketleri ağdan tekrar indirmez. Birincisi "hiç çalışma", ikincisi "çalış ama hızlı çalış" demektir.

    Önbellek diski ne kadar şişirir, nasıl temizlerim#

    Aktif geliştirme yapılan bir makinede derleme önbelleği kolaylıkla onlarca gigabayta ulaşır. docker buildx du komutu güncel kullanımı gösterir. Temizlik için docker builder prune kullanılır; --filter until=168h gibi bir süre filtresiyle yalnızca eski girdileri silmek, güncel önbelleği kaybetmeden yer açmanın en pratik yoludur.

    CI hattımda önbellek çalışmıyor, ne yapmalıyım#

    CI koşucuları genelde her işte temiz başlar, bu yüzden yerel katman deposu boştur. Çözüm --cache-from ve --cache-to bayraklarıyla önbelleği dışarı aktarmaktır. Sağlayıcıdan bağımsız çalışmak istiyorsan registry tabanlı önbellek, GitHub Actions kullanıyorsan type=gha en pratik seçenektir. Çok aşamalı derlemelerde mode=max yazmayı unutma, yoksa ara aşamalar önbelleğe girmez.

    Aynı Dockerfile farklı makinelerde neden farklı sürüyor#

    Önbellek makineye özeldir; yeni bir makinede ilk derleme her zaman tam süre alır. Ayrıca temel imajın yerelde bulunup bulunmaması, ağ hızı, disk türü ve CPU çekirdek sayısı süreyi doğrudan etkiler. BuildKit paralel aşamaları çekirdek sayısına göre yürüttüğü için, çok çekirdekli bir makinede çok aşamalı derlemeler belirgin biçimde daha hızlı biter.

    Kapanış#

    Derleme hızının sırrı egzotik bayraklarda değil, sıralamada. Aklında tutman gereken dört şey: kilit dosyalarını kaynak koddan önce kopyala, .dockerignore olmadan hiçbir optimizasyonun tam çalışmayacağını bil, tekrar eden indirmeleri --mount=type=cache ile kalıcı hâle getir ve CI'da önbelleği mutlaka dışarı aktar. Bunları uyguladığında tipik bir tekrarlanan derleme dakikalardan on saniyeler mertebesine iner; üstelik hiçbir şeyin doğruluğundan ödün vermeden.

    Derleme ve dağıtım altyapını kurarken destek istersen Clou.TR tarafında birkaç seçenek var. Kendi derleme sunucunu kurmak için çok çekirdekli VDS ve bulut sunucu paketlerimize bakabilir, işletim ve bakım yükünü devretmek istersen sunucu yönetimi hizmetimizi değerlendirebilir, yoğun derleme trafiğini karşılayacak donanım arıyorsan dedicated sunucu seçeneğimizi inceleyebilirsin.

    DockerBuildKitPerformans

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.