Docker imajlarını üretim ortamına taşıdığınızda iki sorun aynı anda ortaya çıkar: imaj boyutu ve saldırı yüzeyi. Multi-stage build bu ikisini birlikte ele almanın en pratik yoludur. Aşağıdaki rehber, kavramı adım adım kurar ve ardından güvenlik sıkılaştırmasını katman katman anlatır.
Multi-Stage Build Neden Gerekli
Tek aşamalı bir Dockerfile'da derleyici, paket yöneticisi, test araçları ve kaynak kod aynı imaja girer. Bu imaj çalışma zamanında bu araçların hiçbirine ihtiyaç duymaz; ama hepsi saldırganın erişebileceği hâlde kalır. Multi-stage build, derleme ortamını çalışma ortamından ayırır. Sonuç imaja yalnızca çalıştırmak için gereken dosyalar kopyalanır.
Mekanizma FROM ... AS <isim> ile başlar. Her FROM yeni bir aşama açar; önceki aşamaların dosya sistemi sonraki aşamalara otomatik olarak taşınmaz. Yalnızca COPY --from=<aşama> ile açıkça seçtiğiniz dosyalar geçer.
Önkoşullar ve Sürüm Kapsamı
- Docker Engine ve BuildKit tabanlı
docker build. Multi-stage sözdizimi uzun süredir kararlıdır;COPY --fromdesteği standarttır. --mount=type=cachegibi BuildKit'e özgü özellikler için BuildKit etkin olmalıdır. Bu özellikler klasik derleyicide çalışmaz.- Aşağıdaki örnekler Node.js tabanlı bir uygulama üzerinden ilerler. Sürüm etiketlerini kendi projenizin gereksinimine göre sabitleyin; burada belirli bir sürüm numarası önermiyorum, çünkü doğru seçim projeye bağlıdır.
Temel Multi-Stage Yapı
İki aşamalı tipik bir akış: birinci aşamada bağımlılıkları kurup derleme yaparsınız, ikinci aşamada yalnızca çıktıyı taşırsınız.
# --- Derleme aşaması ---
FROM node:20-alpine AS builder
WORKDIR /app
# Önce manifest dosyalarını kopyala: katman önbelleği bağımlılıklara bağlanır
COPY package.json package-lock.json ./
RUN npm ci
# Kaynak kodu kopyala ve derle
COPY . .
RUN npm run build
# --- Çalışma aşaması ---
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
# Yalnızca üretim bağımlılıkları
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
# Derleme çıktısını builder'dan al
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
Burada dikkat edilecek noktalar:
COPY package.json package-lock.json ./satırıCOPY . .satırından önce gelir. Manifest değişmediği sürece bağımlılık katmanı önbellekten gelir; yalnızca kaynak değiştiğinde derleme yeniden çalışır.npm cikilit dosyasına birebir uyar venpm install'a göre daha öngörülebilirdir.- Çalışma aşamasında
npm ci --omit=devkullanılır; geliştirme bağımlılıkları imaja girmez. nodeimajında hazır gelennodekullanıcısınaUSERile geçilir.
Beklenen davranış: docker build sonrası son imaj, builder aşamasındaki derleyici ve geliştirme bağımlılıklarını içermez. docker image ls çıktısında boyutun tek aşamalı sürüme göre belirgin biçimde küçüldüğünü görürsünüz; kesin oran projeye göre değişir.
.dockerignore Olmadan Bu Yapı Yarım Kalır
COPY . . satırı, .dockerignore yoksa node_modules, .git, yerel .env dosyaları ve derleme çıktılarını da bağlama katar. Bu hem derlemeyi yavaşlatır hem de sırların imaja sızma riskini artırır.
node_modules
.git
.env
.env.*
dist
*.log
.git klasörünün imaja girmesi, geçmişteki sırların ve iç yapının dışarı sızması anlamına gelir. .env dosyasının girmesi ise doğrudan kimlik bilgisi sızıntısıdır.
Güvenlik Sıkılaştırma
Kök Olmayan Kullanıcı
Varsayılan olarak konteyner içindeki süreç root olarak çalışır. Konteyner kaçışı senaryosunda bu, host üzerinde geniş yetki demektir. Çalışma aşamasında USER ile ayrıcalıksız bir kullanıcıya geçin. Alpine tabanlı imajlarda node kullanıcısı hazır gelir; Debian tabanlı imajlarda kullanıcıyı siz oluşturmanız gerekir:
RUN groupadd --system app && useradd --system --gid app app
USER app
Sabitlenmiş Taban İmaj
node:latest gibi kayan etiketler, derlemenin zaman içinde sessizce değişmesine yol açar. Bunun yerine belirli bir sürüm etiketi kullanın. Daha katı bir yaklaşım, imajı digest ile sabitlemektir:
FROM node:20-alpine@sha256:<digest>
Digest sabitleme, etiketin arkasındaki içeriğin değişmemesini garanti eder. Takas: güvenlik güncellemelerini almak için digest'i elle güncellemeniz gerekir.
Derleme Sırlarını İmaja Yazmamak
ARG ve ENV ile verilen değerler imaj geçmişinde görünür. Özel bir npm registry token'ı veya benzeri bir sır gerekiyorsa, BuildKit'in secret mount mekanizmasını kullanın:
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
Derleme sırasında docker build --secret id=npmrc,src=.npmrc . ile verilir. Sır, katmana yazılmaz ve son imajda bulunmaz.
Gereksiz Araçları Çıkarmak
Çalışma imajına kabuk, paket yöneticisi veya ağ araçları bırakmak saldırgana işini kolaylaştırır. Alpine tabanlı imajlar bu açıdan görece yalındır. Daha ileri bir adım, yalnızca çalışma zamanı kütüphanelerini içeren distroless benzeri taban imajlara geçmektir. Bu imajlarda kabuk bulunmaz; hata ayıklama zorlaşır, bu yüzden geçiş bilinçli bir tercih olmalıdır.
Sağlık Kontrolü ve Çalışma Zamanı Kısıtları
Dockerfile'a HEALTHCHECK eklemek, bozuk bir konteynerin trafik almasını engellemeye yardımcı olur:
HEALTHCHECK --interval=30s --timeout=3s \
CMD node -e "require('http').get('http://localhost:3000/health',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"
Çalıştırma sırasında ek kısıtlar uygulanabilir: kök dosya sistemini salt okunur yapmak, geçici dizinleri tmpfs olarak bağlamak, Linux yeteneklerini düşürmek. Bunlar Dockerfile'ın değil, çalıştırma yapılandırmasının konusudur; ama imaj tasarımı bunları mümkün kılacak şekilde yapılmalıdır. Örneğin uygulama yazma gereksinimini yalnızca belirli bir dizine yönlendirmek, salt okunur kök dosya sistemini uygulanabilir kılar.
Yaygın Hatalar ve Etkileri
COPY . .satırını manifest kopyalamadan önce kullanmak. Her kaynak değişikliğinde bağımlılıklar yeniden kurulur; derleme süresi artar. Güvenlik etkisi yoktur, ama geri bildirim döngüsü uzar.- Sırları
ARG/ENVile geçirmek. Değer imaj geçmişinde kalır ve imajı çeken herkes görebilir.docker historyile ortaya çıkar. - Çalışma aşamasında
USERbelirtmemek. Konteyner root çalışır; kaçış senaryosunda etki alanı büyür. - Geliştirme bağımlılıklarını üretim imajına bırakmak. Hem boyut artar hem de ek araçlar saldırı yüzeyi oluşturur.
.dockerignore'u atlamak..gitve.envimaja girer; sızıntı riski doğar.- Kayan taban etiketi kullanmak. Aynı Dockerfile farklı zamanlarda farklı imaj üretir; üretimde tekrarlanabilirlik kaybolur.
Doğrulama
İmajı oluşturduktan sonra şu kontrolleri yapabilirsiniz:
docker image history <imaj>ile katmanları ve boyutlarını inceleyin; beklenmedik katman var mı bakın.docker run --rm <imaj> idile sürecin hangi kullanıcıyla çalıştığını doğrulayın.docker run --rm <imaj> ls /appile çalışma aşamasında beklenmeyen dosya olup olmadığını kontrol edin.- İmaj taraması için
docker scout cves <imaj>veya tercih ettiğiniz bir tarayıcı kullanın.
Bu kontroller, yapılandırmanın gerçekten amaçlandığı gibi sonuçlandığını gösterir; varsayımla ilerlemek yerine imajın kendisini ölçmek gerekir.
Özet
Multi-stage build iki işi birden yapar: son imajı yalnızca çalışma zamanı gereksinimleriyle sınırlar ve derleme araçlarını üretimden uzak tutar. Güvenlik sıkılaştırması bunun üzerine eklenir: ayrıcalıksız kullanıcı, sabitlenmiş taban imaj, imaja yazılmayan sırlar ve gereksiz araçların çıkarılması. Her adımı imajın kendisini inceleyerek doğrulayın; boyut ve katman içeriği, yapılandırmanın doğru çalıştığının en somut göstergesidir.