Container imajı boyutu, dağıtım süresi, güvenlik yüzeyi ve cold start performansı arasında doğrudan bir ilişki vardır. Tek aşamalı bir Dockerfile, derleme araçlarını, test bağımlılıklarını ve geçici dosyaları final imaja taşıdığında imaj hızla şişer; bu durum registry maliyetini artırır, pod çekme süresini uzatır ve saldırı yüzeyini genişletir. Multi-stage build, aynı Dockerfile içinde birden fazla FROM talimatı tanımlayarak her aşamada farklı bir temel imaj veya farklı bir komut kümesi çalıştırmanıza izin verir; yalnızca çalışma zamanında gereken dosyalar son aşamaya kopyalanır.
Tek aşamalı imajın gizli maliyeti
Örneğin bir .NET uygulamasını mcr.microsoft.com/dotnet/sdk:8.0 üzerinde derleyip aynı katmanda çalıştırmak pratik görünür; ancak SDK imajı runtime imajına göre yüzlerce megabayt daha büyüktür. İmaj içinde dotnet CLI, derleyici, NuGet cache ve geliştirme araçları bulunur. Üretimde bunların hiçbiri gerekmez. Benzer şekilde Node.js tabanlı frontend build sürecinde node_modules, webpack ve babel gibi araçlar final imaja kalırsa imaj boyutu gigabayt sınırlarına yaklaşabilir. Bu yalnızca disk maliyeti değildir: Kubernetes ortamında her yeni node imajı çeker, her rolling update registry trafiği oluşturur ve container runtime katman cache'i verimsiz kullanılır.
Multi-stage yapının anatomisi
Multi-stage Dockerfile tipik olarak üç mantıksal bölümden oluşur: bağımlılık çözümü, derleme ve runtime. Her bölüm kendi FROM satırı ile başlar ve isteğe bağlı bir AS takma adı alır. Sonraki aşamalar COPY --from=build /app/publish . gibi talimatlarla önceki aşamadan yalnızca gerekli artefaktları alır. Docker BuildKit etkin olduğunda paralel aşama çalıştırma, daha iyi cache kullanımı ve gizli dosya optimizasyonu sağlanır.
# syntax=docker/dockerfile:1.7
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS restore
WORKDIR /src
COPY *.sln .
COPY src/MyApp/*.csproj ./src/MyApp/
RUN dotnet restore src/MyApp/MyApp.csproj
FROM restore AS build
COPY . .
RUN dotnet publish src/MyApp/MyApp.csproj -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
RUN adduser --disabled-password --gecos "" appuser
COPY --from=build /app/publish .
USER appuser
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]
Bu örnekte restore ve build aşamaları SDK imajında kalır; runtime aşaması yalnızca ASP.NET runtime içerir. USER appuser ile root olmayan kullanıcı kullanımı, container güvenliği için kritik bir adımdır ve multi-stage yapıda kolayca uygulanır.
Cache stratejisi ve katman sıralaması
Multi-stage build'in performans avantajı yalnızca küçük imaj değildir; doğru sıralanmış COPY ve RUN adımları build cache'i dramatik şekilde iyileştirir. Bağımlılık dosyalarını (*.csproj, package-lock.json, go.mod) kaynak kodundan önce kopyalamak, bağımlılıklar değişmediği sürece restore adımının cache'den gelmesini sağlar. Kaynak kod her commit'te değişse bile restore katmanı yeniden çalışmaz.
BuildKit cache mount
BuildKit ile RUN --mount=type=cache kullanarak NuGet, npm veya pip cache dizinlerini kalıcı hale getirebilirsiniz. CI ortamında her build sıfırdan başlamak yerine paket indirme süresi dakikalardan saniyelere iner:
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore src/MyApp/MyApp.csproj
Bu yaklaşım özellikle monorepo yapılarında, onlarca servisin paralel build edildiği pipeline'larda toplam süreyi ölçülebilir şekilde düşürür.
Güvenlik ve saldırı yüzeyi
Final imajda derleyici, shell veya paket yöneticisi bulunmaması, container breakout senaryolarında saldırganın kullanabileceği araç sayısını azaltır. Distroless imajlar (gcr.io/distroless) bu felsefenin uç noktasıdır: yalnızca uygulama ve minimum runtime kütüphaneleri vardır, /bin/sh bile yoktur. Multi-stage build ile önce tam bir SDK imajında derleyip sonra distroless runtime'a kopyalamak mümkündür.
- Secret sızıntısı: Build argümanları (
ARG) imaj geçmişinde kalabilir;docker historyile görülebilir. Gizli bilgiler için BuildKit secret mount kullanın. - SBOM ve imza: Küçük imajlar taramayı hızlandırır; Cosign veya Notation ile imzalanan minimal imajlar tedarik zinciri güvenliğini güçlendirir.
- CVE yoğunluğu: Az paket, az bilinen güvenlik açığı. Slim veya alpine tabanlı runtime ile karşılaştırma yaparken musl libc uyumluluğunu test edin.
Dil ve ekosisteme özel kalıplar
Go statik binary
Go uygulamaları CGO kapalı derlendiğinde scratch imajına kopyalanabilir; final imaj yalnızca birkaç megabayt olur:
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /bin/app ./cmd/api
FROM scratch
COPY --from=build /bin/app /app
ENTRYPOINT ["/app"]
Node.js frontend
React veya Vue projelerinde build aşaması Node imajında çalışır; nginx alpine imajına yalnızca dist/ klasörü kopyalanır. API anahtarları build-time ARG ile verilmemeli; runtime'da ortam değişkeni olarak enjekte edilmelidir.
CI/CD entegrasyonu
Pipeline'da docker buildx build --push --cache-from type=registry --cache-to type=registry,mode=max ile remote cache paylaşımı sağlanır. Her geliştirici makinesi ve CI runner aynı cache katmanlarını kullanır. Multi-stage Dockerfile'da aşama sayısı arttıkça cache anahtarlarının doğru yapılandırılması gerekir; aksi halde gereksiz rebuild oluşur.
İmaj boyutunu pipeline metriği olarak izleyin. Ani boyut artışı genellikle yanlışlıkla debug sembollerinin, test veritabanının veya .git dizininin kopyalandığını gösterir. .dockerignore dosyası multi-stage kadar önemlidir: gereksiz dosyalar build context'e hiç girmemelidir.
Test ve doğrulama
Build aşamasına birim testleri eklemek için ayrı bir test stage tanımlayabilirsiniz. CI, test stage başarısız olursa runtime imajını push etmez:
FROM build AS test
RUN dotnet test --no-restore --configuration Release
FROM runtime AS final
# test stage başarılı olmadan buraya gelinmez (CI koşulu ile)
Container yapısal testleri (Container Structure Tests, dive, trivy) final imajın beklenen kullanıcı, port ve dosya izinlerini doğrular. Multi-stage sonrası imajda yanlışlıkla kalan dosyaları tespit etmek için dive aracı etkili bir geri bildirim sağlar.
Yaygın hatalar ve çözümleri
- Yanlış aşamadan kopyalama:
COPY --from=0yerine isimli stage kullanın; Dockerfile değişince indeks kayması yaşanmaz. - Platform uyumsuzluğu: Apple Silicon geliştiriciler
--platform=linux/amd64ile cross-build yapmalıdır. - Runtime kütüphane eksikliği: Alpine'a geçerken glibc bağımlılıkları test edilmeli; ICU, timezone ve sertifika paketleri runtime stage'e eklenmelidir.
- Health check eksikliği: Minimal imajda curl olmayabilir; HTTP health check için uygulama içi endpoint veya
wgetyerine exec probe tercih edin.
Ölçüm ve hedef metrikler
İyi tasarlanmış multi-stage pipeline için tipik hedefler: final imaj boyutu tek aşamaya göre en az yüzde 60 küçülme, CI build süresinde cache hit ile yüzde 40 iyileşme, Trivy taramasında kritik CVE sayısında belirgin düşüş. Bu metrikleri sprint retrospektiflerinde takip etmek, altyapı yatırımının somut getirisini gösterir.
Multi-stage build bir Dockerfile sözdizimi özelliğinden öte, üretim kalitesinde container stratejisinin temel taşıdır. Derleme ve runtime endişelerini ayırarak hem operasyonel verimlilik hem de güvenlik kazanırsınız; BuildKit cache, distroless runtime ve CI remote cache ile birleştirildiğinde etkisi katlanarak artar.
Katman paylaşımı ve OCI uyumluluğu
Multi-stage build'de kullanılmayan aşamalar final imaja dahil edilmez; yalnızca referans alınan katmanlar taşınır. BuildKit, aynı base imajı paylaşan aşamalar arasında katman deduplication uygular. OCI (Open Container Initiative) spesifikasyonuna uygun imajlar, farklı registry'ler arasında taşınabilir kalır. docker buildx imagetools inspect ile multi-platform manifest listesi doğrulanır; arm64 ve amd64 artefaktları tek tag altında birleştirilir.
Üretim kontrol listesi
- Final stage'te yalnızca runtime bağımlılıkları kaldı mı?
- Non-root user tanımlı mı?
.dockerignorebuild context'i sınırlıyor mu?- Build secret'ları
ARGyerine secret mount ile mi veriliyor? - İmaj boyutu ve CVE sayısı pipeline'da threshold ile kontrol ediliyor mu?