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 ad verdiniz 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.