Bir uygulamayı derlemek için gereken araçlar (derleyici, geliştirme kütüphaneleri, test araçları) çalıştırmak için gerekenlerden çok daha fazladır. Tek bir Dockerfile ile build eden bir imaj bu araçların hepsini üretime taşır: gereksiz yere büyük, daha yavaş indirilen ve saldırı yüzeyi geniş bir imaj. Multi-stage build bu sorunu tek bir Dockerfile içinde, birden fazla FROM kullanarak çözer.
Fikir: derle bir yerde, çalıştır başka yerde
Her FROM yeni bir “aşama” (stage) başlatır. Bir aşamada derlersiniz; son aşamada ise yalnızca derleme çıktısını COPY --from ile alıp temiz bir imaja koyarsınız. Önceki aşamalar nihai imaja girmez.
# Aşama 1 — derleme (büyük araç seti burada)
FROM temel-imaj AS build
RUN head -c 30000000 /dev/zero > /buyuk-arac-seti.bin
RUN echo "derlenmis-uygulama" > /app.bin
# Aşama 2 — nihai imaj (yalnızca çıktı)
FROM temel-imaj
COPY --from=build /app.bin /app.bin
CMD ["/bin/cat", "/app.bin"]
Burada 30 MB’lık “araç seti” yalnızca build aşamasında vardır. Aynı işi tek aşamada yapan sürümle karşılaştıralım:
$ docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | sort
ms:multi 21.1MB
ms:single 51.2MB
Tek aşamalı imaj 51,2 MB, multi-stage imaj 21,1 MB — fark, araç setinin nihai imaja girmemesinden gelir. Gerçek projelerde bu fark çok daha dramatiktir (derleyici ve bağımlılıklar yüzlerce MB olabilir).
Aşamalara ad verin
AS ad ile aşamalara isim verebilirsiniz; COPY --from=build gibi referanslar sıraya (--from=0) bağımlı olmaz ve Dockerfile’a yeni aşama eklediğinizde bozulmaz. Başka bir imajdan da doğrudan dosya kopyalayabilirsiniz:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
Belirli bir aşamada durmak: --target
Hata ayıklarken ya da sadece test aşamasını çalıştırmak istediğinizde --target ile build’i o aşamada durdurabilirsiniz:
docker build --target build -t ornek:build .
Dikkat: --target build ile üretilen imaj, nihai imaj değil build aşamasının tamamıdır; bizim denememizde 51,2 MB çıktı. Bu imajı üretime göndermemeniz gerektiğini unutmayın.
BuildKit gereksiz aşamaları atlar
Dockerfile’a şöyle bir test aşaması ekleyelim, ama nihai aşama bundan hiçbir şey kopyalamasın:
FROM temel-imaj AS test
COPY --from=build /app.bin /app.bin
RUN echo "testler gecti"
BuildKit (Docker’ın modern build motoru ve varsayılan builder) yalnızca hedef aşamanın bağımlı olduğu aşamaları çalıştırır. --progress=plain çıktısını incelediğimizde test aşamasının hiç çalışmadığını doğruladık. Bu özelliği “isteğe bağlı” aşamalar için kullanabilirsiniz: test aşamasını yalnızca CI’da --target test ile çalıştırın, normal build’de hiç çalışmasın.
Gerçek dünya kalıbı
Yaygın bir kalıp, derleme için tam bir SDK imajı, çalıştırma için minimal bir imaj kullanmaktır. Örneğin bir Go uygulamasında:
FROM golang:1.26 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /bin/uygulama .
FROM scratch
COPY --from=build /bin/uygulama /bin/uygulama
ENTRYPOINT ["/bin/uygulama"]
Statik derlenmiş bir ikili scratch (boş) imajda çalışabilir; sonuç birkaç MB’lık, içinde kabuk bile bulunmayan bir imajdır. Node.js, Python ya da Java gibi çalışma zamanı gerektiren dillerde son aşama genellikle -slim, alpine ya da distroless bir imaj olur (bkz. Container Image Güvenliği).
Güvenlik kazancı da vardır: derleyici, paket yöneticisi ve test araçları nihai imajda olmadığı için, bir saldırgan container’a sızsa bile elinde kullanabileceği araç çok azdır.
Hızlı kontrol listesi
- Derleme araçları nihai imajın dışında mı?
- Aşamalara
AS adverdiniz mi? - Bağımlılık kurulumu (
COPY package.json+RUN npm ci) kaynak koddan önce geliyor mu? (bkz. Build Cache) - Test gibi isteğe bağlı işler ayrı bir aşamada mı?
İlk adımlar için Dockerfile Yazmaya Giriş yazısına, tüm bunların birleştiği uygulamalı bir örnek için Uçtan Uca Örnek Proje yazısına bakabilirsiniz.