Skip to main content

Dockerfile Oluşturucu

Node, Python, Go veya Java için çok aşamalı Dockerfile üretin. Root olmayan kullanıcı, katman önbelleği ve uyumlu bir .dockerignore ile birlikte.

Veriler tarayıcınızdan çıkmazGüncelleme: Temmuz 2026
Paket yöneticisi

Örneğin 20-alpine ya da 3.12-slim. Boş bırakılırsa derleme adımı üretilmez.

Seçenekler
Çok aşamalı
Root olmayan kullanıcı
Sağlık kontrolü

Manifest dosyaları kaynak kodundan önce kopyalanır, böylece tek satırlık bir değişiklik tüm bağımlılıkları yeniden kurmaz. Çok aşamalı derlemede son imajta derleyici ve derleme önbelleği yer almaz.

Özet (TL;DR)

  • Kaynak dosyalar bağımlılık kurulumundan önce kopyalanırsa tek satırlık bir değişiklik bütün bağımlılık ağacını yeniden kurdurur; araç manifest dosyalarını her zaman önce kopyalar.
  • Çok aşamalı derlemede son imaja derleyici, build önbelleği ve kaynak ağacı girmez; Go için runtime tabanı alpine:3.20, Java için eclipse-temurin:21-jre olur.
  • Container varsayılan olarak uid 0 ile çalışır. Araç node imajındaki hazır node kullanıcısını kullanır, diğerlerinde uid 1001 ile gerçek bir hesap açar.
  • .dockerignore yoksa node_modules ve .git build context'ine girer, bu da hem yavaşlatır hem de katman önbelleğini bozar.

1.2 GB imaj, 90 MB olması gerekirken

Docker'da ilk çalışan Dockerfile'ı yazmak kolay: FROM, COPY . ., RUN npm install, CMD. Sorun docker images çıktısına baktığınızda başlıyor. Küçük bir Node servisi 1.2 GB yer kaplıyor ve içinde derleyici, npm önbelleği, test dosyaları, .git klasörü ve iki kopya node_modules var. Aynı servis doğru yazılmış bir Dockerfile ile 90 MB civarında duruyor. Fark, imajda ne olduğu değil, imajda gereksiz olarak ne kaldığı.

Katman önbelleği ve kopyalama sırası

Dockerfile'daki her komut bir katman üretiyor ve Docker bir katmanı, girdileri değişmediği sürece yeniden çalıştırmıyor. Bir katman geçersiz olduğunda ise ondan sonraki her katman da geçersiz oluyor. Bu iki cümle, aşağıdaki iki dosya arasındaki farkı açıklıyor.

dockerfile
# YANLIŞ: kaynak önce kopyalanıyorFROM node:20-alpineWORKDIR /appCOPY . .RUN npm ci          # her kod değişikliğinde baştan çalışırCMD ["node", "dist/index.js"] # DOĞRU: manifest önceFROM node:20-alpineWORKDIR /appCOPY package.json package-lock.json* ./RUN npm ci          # yalnızca lock dosyası değişince çalışırCOPY . .CMD ["node", "dist/index.js"]

İlk dosyada tek bir satır düzelttiğinizde COPY . . katmanı değişiyor, dolayısıyla npm ci de yeniden koşuyor ve build süresi bağımlılık sayısına bağlı olarak dakikalara çıkıyor. İkincisinde aynı düzeltme yalnızca son katmanı etkiliyor, build birkaç saniyede bitiyor. Araç lock dosyalarını sondaki yıldızla kopyalıyor, çünkü COPY var olmayan bir yol için hata veriyor ve hiçbir projede üç lock dosyasının üçü birden bulunmuyor.

Çok aşamalı derleme

Çok aşamalı yapıda üç FROM var: bağımlılıkları çözen deps, kaynakları derleyen build ve yalnızca çalıştırmaya yarayan runtime. Son imaja sadece runtime aşamasında kalan şeyler giriyor, öncekiler build makinesinde kalıyor. Kazanç en çok derleyici gerektiren dillerde görünüyor.

Çalışma ortamıBuild tabanıRuntime tabanıSon imajda olmayan
Nodenode:20-alpinenode:20-alpineKaynak ağacı, build önbelleği
Gogolang:...alpine:3.20Go toolchain'in tamamı
Javamaven:...eclipse-temurin:21-jreJDK ve Maven
Pythonpython:...python:...Derleme sırasında gereken build araçları
PHPphp:...php:...Composer dev bağımlılıkları
Go tarafında scratch yerine alpine seçilir, çünkü scratch'te CA sertifikası ve kabuk yoktur.

Go'da fark en dramatik olanı: statik derlenmiş bir binary'nin çalışması için toolchain gerekmiyor, dolayısıyla 800 MB'lık golang imajı yerine 8 MB'lık alpine yetiyor. Java tarafında JDK ile JRE ayrımı aynı işi görüyor.

Root ile çalışmak ve .dockerignore

Container aksi söylenmedikçe uid 0 ile, yani root olarak çalışıyor. Bu, uygulama içindeki bir açığın doğrudan container içinde root yetkisi anlamına gelmesi demek ve bir container kaçışıyla birleştiğinde host tarafında gerçek bir sorun. Araç nonRoot seçildiğinde işi yarım bırakmıyor: node imajında zaten var olan node hesabını kullanıyor, diğer imajlarda uid 1001 ile gerçek bir kullanıcı açıyor ve çalışma dizininin sahipliğini ona veriyor. Alpine ve Debian'da adduser bayrakları farklı olduğu için tag içinde alpine geçip geçmediğine bakıyor.

.dockerignore ise en sık atlanan dosya. Yoksa docker build çağrısı bulunduğunuz dizinin tamamını daemon'a gönderiyor: node_modules, .git geçmişi, .env dosyaları, coverage raporları. Bu hem ilk adımı yavaşlatıyor hem de COPY . . üzerinden imaja gereksiz dosya sokuyor. Araç Dockerfile ile birlikte bir .dockerignore da üretiyor ve **/.env deseni sayesinde ortam dosyaları build context'ine hiç girmiyor.

CMD ile ENTRYPOINT farkı

CMD varsayılan komutu belirtiyor ve docker run imaj baska-komut yazdığınızda tamamen değişiyor. ENTRYPOINT ise sabit kalıyor, komut satırında verdiğiniz şeyler ona argüman olarak ekleniyor. Uygulama sunucusu çalıştıran imajlarda CMD yeterli; imajı bir CLI aracı gibi kullandıracaksanız ENTRYPOINT daha uygun.

Uyarı

Komutu CMD node dist/index.js biçiminde (shell form) yazmayın. Bu biçim süreci /bin/sh -c altına alıyor, sh de SIGTERM sinyalini iletmediği için container her durdurulduğunda 10 saniyelik kill zaman aşımını doldurmayı bekliyor. Araç bu yüzden komutu her zaman CMD ["node", "dist/index.js"] gibi exec form olarak üretiyor.

Sıkça Sorulan Sorular

İmajım neden bu kadar büyük?
Üç yaygın sebep var. Birincisi build context: .dockerignore yoksa node_modules ve .git imaja giriyor. İkincisi tek aşamalı yapı: derleyici, paket yöneticisi önbelleği ve kaynak ağacı son imajda kalıyor. Üçüncüsü taban imajı: node:20 ile node:20-alpine arasında birkaç yüz megabaytlık fark var. docker history imaj çıktısı hangi katmanın kaç megabayt eklediğini gösterir, oradan başlayın.
Build önbelleği neden hiç tutmuyor?
Neredeyse her zaman kopyalama sırası yüzünden. COPY . . satırı kurulum adımından önce geliyorsa her kod değişikliği o katmanı geçersiz kılıyor ve sonrasındaki her şey yeniden koşuyor. Manifest dosyalarını ayrı kopyalayıp kurulumu hemen ardından yapın. İkinci sebep, Dockerfile içinde tarih veya rastgele değer üreten bir ARG kullanmak; bu her build'de girdiyi değiştirir.
alpine mi slim mi?
Alpine musl libc kullanıyor, glibc değil. Saf JavaScript veya Go için sorun çıkmaz ve imaj belirgin biçimde küçüktür. Native uzantı derleyen Python paketlerinde (numpy, pandas, psycopg2) alpine'de tekerlek bulunamadığı için kaynaktan derleme başlar, build hem uzar hem de bazen imaj slim'den büyük çıkar. Python tarafında varsayılan olarak slim, Node ve Go tarafında alpine mantıklı bir başlangıç.
Küçük bir proje için çok aşamalı derleme şart mı?
Derleme adımı olmayan bir projede kazanç sınırlı: Python'da doğrudan çalışan bir uygulamada tek aşama yeterli olabilir. Ama build komutu olan her projede fark hemen görünür hale geliyor, çünkü TypeScript derleyicisi, bundler ve dev bağımlılıkları son imajda kalmıyor. Go'da proje ne kadar küçük olursa olsun çok aşamalı yapmaya değer, orada oran 800 MB ile 15 MB arasında.
Container neden hemen kapanıyor?
Docker, PID 1 olarak çalışan süreç bittiğinde container'ı sonlandırır. Uygulamanız arka plana geçen bir süreç başlatıp çıkıyorsa (bazı servis yöneticileri veya & ile başlatılan komutlar) container da çıkar. Uygulamayı ön planda çalıştırın. Diğer sık sebep, CMD'deki yolun imaj içinde bulunmaması: docker run --rm -it imaj sh ile içeri girip dosyanın gerçekten orada olup olmadığına bakın. Çok aşamalı yapıda derleme çıktısı runtime aşamasına kopyalanmamışsa da aynı hata görülür.

Mustafa Kürşad BAŞER

Yazılım Mühendisi

Karmaşık sorunları zarif çözümlerle buluşturan, öğrendiklerini paylaşarak değer katmayı seven bir yazılım mühendisi.

Hızlı Erişim

Bağlantı

© 2026 Mustafa Kürşad BAŞER. Tüm hakları saklıdır.