Bir gün deploy sürecinizin yavaşladığını, sunucu diskinizin dolduğunu ya da CI/CD boru hattınızın her seferinde uzun uzun imaj çektiğini fark ederseniz, büyük ihtimalle suçlu şişkin Docker imajlarınızdır. Basit bir Node.js veya Python uygulaması, hiç dikkat edilmediğinde 1,2 GB'a kadar çıkabilir; oysa aynı uygulama doğru tekniklerle 80-150 MB bandına indirilebilir. Bu, on kat küçülme demektir ve doğrudan daha hızlı dağıtım, daha düşük depolama maliyeti ve daha küçük saldırı yüzeyi olarak geri döner.
Bu rehberde imajların neden şiştiğini adım adım açıklayacak, ardından hafif temel imaj seçimi, çok aşamalı derleme, katman birleştirme, .dockerignore disiplini ve dive ile katman analizi gibi kanıtlanmış yöntemleri gerçek komutlarla göstereceğiz. Kıdemli bir sistem yöneticisinin masasında oturmuş gibi düşünün; ölçülebilir kazanımlarla, üretimde işe yarayan pratik örneklerle ilerleyeceğiz. Docker kavramlarına henüz hakim değilseniz önce Docker nedir başlangıç rehberimize göz atmanızı öneririm.
Docker İmajları Neden Şişer?#
Bir Docker imajı, üst üste bindirilmiş salt-okunur katmanlardan oluşur. Dockerfile içindeki her FROM, RUN, COPY ve ADD komutu yeni bir katman yaratır. Buradaki en kritik nokta şudur: bir katmanda oluşturulan dosya, sonraki bir katmanda silinse bile eski katmanda kalmaya devam eder. Yani RUN apt-get install ... ile 200 MB paket kurup, ayrı bir RUN rm -rf ... ile temizlerseniz, imaj hâlâ o 200 MB'ı içinde taşır. Silme işlemi yalnızca üstteki katmanda dosyayı görünmez yapar, alttaki katmandan atmaz.
İmajların şişmesinin başlıca nedenlerini bir arada görelim:
- Ağır temel imaj:
ubuntuveyanodegibi tam işletim sistemi imajları yüzlerce megabayt taban getirir. - Derleme araçlarının kalması:
gcc,make,build-essential,npm,pipgibi araçlar yalnızca derleme aşamasında gereklidir ama nihai imajda kalırlar. - Paket yöneticisi önbelleği:
apt,apk,npm,pipher kurulumdan sonra indeks ve indirme önbelleğini geride bırakır. - Geliştirme bağımlılıkları:
devDependencies, test dosyaları, dokümantasyon ve örnek klasörleri. - Kaynak dosyaların kopyalanması:
.gitklasörü,node_modules, log dosyaları ve gizli anahtarlar imaja sızabilir.
Bu maddelerin her biri hem boyutu büyütür hem de güvenlik açısından risk taşır. İmajınızdaki her ikili dosya ve kütüphane, potansiyel bir güvenlik açığı yüzeyidir. Amacımız yalnızca uygulamanın çalışması için gereken en küçük dosya kümesini bırakmaktır.
Doğru Temel İmajı Seçmek: Alpine ve Distroless#
En büyük ve en kolay kazanç, doğru temel imajı seçmekten gelir. Aynı uygulama için farklı tabanların getirdiği yükü karşılaştıralım.
| Temel imaj | Yaklaşık boyut | Kullanım durumu |
|---|---|---|
node:20 | ~1000 MB | Kaçının; tam Debian + tüm araçlar |
node:20-slim | ~240 MB | Debian tabanlı, hafifletilmiş |
node:20-alpine | ~135 MB | Musl libc, çok küçük, dikkatli kullanılmalı |
gcr.io/distroless/nodejs20 | ~110 MB | Kabuk yok, yalnızca çalışma zamanı |
slim varyantları çoğu proje için güvenli ve pratik bir başlangıçtır. Alpine Linux ise musl libc ve busybox kullandığı için çok daha küçüktür, ancak glibc bağımlılığı olan bazı ikili paketlerde (özellikle bazı Node yerel modülleri veya Python bilimsel kütüphaneleri) uyumsuzluk yaşayabilirsiniz. Bu durumu test etmeden üretime almayın.
Distroless imajlar bir adım öteye gider: içlerinde kabuk (sh), paket yöneticisi veya ls gibi araçlar dahi yoktur. Yalnızca uygulamanızı çalıştıracak çalışma zamanı bulunur. Bu, hem boyutu hem de saldırı yüzeyini dramatik biçimde düşürür çünkü bir saldırgan konteynere sızsa bile elinde çalıştırabileceği hiçbir araç bulamaz.
# Ağır ve savunmasız
FROM node:20
# İyi bir orta yol
FROM node:20-slim
# En küçük ve en güvenli çalışma zamanı
FROM gcr.io/distroless/nodejs20-debian12
Temel imajınızı seçerken sürüm etiketini de sabitleyin. node:20 yerine node:20.11-slim gibi belirli bir sürüm kullanmak, hem tekrarlanabilir derlemeler sağlar hem de sürpriz boyut değişimlerinin önüne geçer. Sürüm ve yapılandırma disiplinini kod deposunda takip etmek için Git temelleri rehberimizdeki yaklaşımdan faydalanabilirsiniz.
Çok Aşamalı Derleme (Multi-Stage Build)#
İmaj küçültmenin en güçlü tekniği çok aşamalı derlemedir. Fikir basittir: bir aşamada uygulamayı derlemek için gereken tüm ağır araçları kullanırsınız, sonra yalnızca üretilen çıktıyı temiz ve minimal bir ikinci aşamaya kopyalarsınız. Derleme araçları, ara dosyalar ve önbellek nihai imaja hiç girmez.
Aşağıda tipik bir Node.js uygulaması için çok aşamalı bir Dockerfile görüyorsunuz:
# 1. Aşama: derleme
FROM node:20-slim AS builder
WORKDIR /app
# Önce yalnızca bağımlılık dosyalarını kopyala (önbellek için)
COPY package*.json ./
RUN npm ci
# Kaynak kodu kopyala ve derle
COPY . .
RUN npm run build && npm prune --production
# 2. Aşama: çalışma zamanı
FROM node:20-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
# Yalnızca gerekli çıktıyı taşı
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
Burada builder aşaması derleme araçlarını, tüm devDependencies'i ve kaynak kodu içerir; ama nihai imaja yalnızca dist, üretim node_modules ve package.json taşınır. Sonuç, tek aşamalı bir imaja kıyasla genellikle yüzde 60-80 daha küçük olur.
Aynı yaklaşım derlenen dillerde daha da etkilidir. Bir Go uygulamasını statik derleyip distroless veya scratch üzerine koyduğunuzda imaj boyutu birkaç megabayta iner:
FROM golang:1.22 AS builder
WORKDIR /src
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/server .
# Boş bir imaj: yalnızca ikili dosya
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/server"]
-ldflags="-s -w" bayrağı hata ayıklama sembollerini atarak ikili dosyayı da küçültür. scratch tamamen boş bir tabandır; TLS sertifikaları dışında hiçbir şey içermez. Bu Go örneğinde nihai imaj çoğu zaman 10 MB'ın altındadır.
Katmanları Birleştirmek ve Önbelleği Temizlemek#
Yazının başında değindiğimiz gibi, bir katmanda oluşturulup başka katmanda silinen dosya imajda kalır. Bu yüzden kurulum, kullanım ve temizlik işlemlerini tek bir RUN komutunda zincirlemek şarttır. Aşağıdaki iki örnek arasındaki fark, doğrudan onlarca megabayta karşılık gelir.
Yanlış yaklaşım, önbelleği ayrı katmanda bırakır:
RUN apt-get update
RUN apt-get install -y curl nginx
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*
Doğru yaklaşım, hepsini tek katmanda toplar ve önbelleği aynı katman içinde siler:
RUN apt-get update && \
apt-get install -y --no-install-recommends curl nginx && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
--no-install-recommends bayrağı, önerilen ama zorunlu olmayan paketlerin kurulmasını engelleyerek ekstra tasarruf sağlar. Alpine tabanında ise apk için benzer bir kalıp kullanılır:
RUN apk add --no-cache curl nginx
--no-cache bayrağı, apk'nın indeks önbelleğini hiç yazmadan kurulum yapmasını sağlar; ayrıca temizlik komutuna gerek kalmaz. Derleme için geçici paketlere ihtiyaç duyduğunuzda --virtual grupları çok kullanışlıdır:
RUN apk add --no-cache --virtual .build-deps gcc musl-dev python3-dev && \
pip install --no-cache-dir -r requirements.txt && \
apk del .build-deps
Burada derleme paketleri geçici bir grupta kurulur, pip install çalıştırılır ve aynı katmanın sonunda apk del ile grup tamamen kaldırılır. Python tarafında pip install --no-cache-dir mutlaka kullanılmalıdır; aksi halde pip indirdiği tekerlek dosyalarını önbellekte tutar.
Gereksiz Dosyaları Dışarıda Bırakmak#
COPY . . yazdığınızda, çalışma dizinindeki her şeyi imaja kopyalarsınız: .git geçmişi, yerel node_modules, log dosyaları, ortam dosyaları ve hatta gizli anahtarlar. Bunun önüne geçmek için proje kök dizinine .dockerignore dosyası koyarsınız. Bu dosya, .gitignore ile aynı mantıkla çalışır ve derleme bağlamına hangi dosyaların hiç gönderilmeyeceğini belirler.
# Sürüm kontrolü ve düzenleyici
.git
.gitignore
.vscode
# Bağımlılıklar ve derleme çıktıları
node_modules
dist
build
coverage
# Ortam ve gizli bilgiler
.env
.env.*
*.pem
*.key
# Loglar ve geçici dosyalar
*.log
tmp
.DS_Store
# Docker ve dokümantasyon
Dockerfile
docker-compose.yml
README.md
.dockerignore iki yönlü fayda sağlar. Birincisi, imaja gereksiz ve hassas dosyaların girmesini engeller; .env veya *.pem dosyalarının imaja sızması ciddi bir güvenlik olayıdır. İkincisi, Docker'ın sunucuya gönderdiği derleme bağlamını küçülterek build süresini kısaltır. Büyük bir .git klasörü veya yerel node_modules olmadan bağlam saniyeler içinde hazırlanır.
Bir başka iyi alışkanlık, derleme sırasında sık değişen dosyaları en sona koymaktır. package.json'ı kaynak koddan önce kopyalayıp bağımlılıkları önce kurmak, kaynak kod değişse bile bağımlılık katmanının önbellekten gelmesini sağlar. Bu, hem yerel geliştirmede hem de CI/CD'de derleme süresini dakikalar mertebesinde azaltır. Sunucu tarafı yapılandırmalarınızı bir depoda düzenli tutmak isterseniz kod ve altyapı yönetimini birlikte ele alan sunucu yönetimi hizmetimiz bu disiplini kurmanıza yardımcı olur.
dive ile Katman Analizi#
İmajınızın nerede şiştiğini görmeden optimize etmek karanlıkta ateş etmeye benzer. İşte burada dive aracı devreye girer. dive, bir imajın her katmanını gezmenizi, hangi katmanda hangi dosyaların eklendiğini görmenizi ve boşa giden alanı ölçmenizi sağlar. En değerli metriği "verimlilik puanı"dır; imajın ne kadarının gerçekten gerekli olduğunu yüzde olarak gösterir.
Kurulum ve çalıştırma oldukça basittir:
# İkili dosyayı indir (Linux)
DIVE_VERSION=0.12.0
curl -OL https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.deb
sudo dpkg -i dive_${DIVE_VERSION}_linux_amd64.deb
# Bir imajı analiz et
dive benim-uygulamam:latest
Kurulum yapmadan doğrudan Docker üzerinden de çalıştırabilirsiniz:
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest benim-uygulamam:latest
Arayüzde sol tarafta katmanları, sağ tarafta o katmanın dosya ağacını görürsünüz. Kırmızı ile işaretlenen dosyalar, sonraki katmanlarda değiştirilmiş veya silinmiş ama yer kaplamaya devam eden dosyalardır; bunlar ilk avlanacak hedeflerinizdir. dive ayrıca CI boru hattında otomatik kapı olarak da kullanılabilir:
# Verimlilik %90'ın altındaysa build başarısız olsun
CI=true dive benim-uygulamam:latest \
--lowestEfficiency 0.90 \
--highestWastedBytes 20MB
Bu satırı CI adımınıza koyduğunuzda, biri farkında olmadan imajı şişiren bir değişiklik yaptığında boru hattı otomatik olarak uyarır. Böylece optimizasyon bir kereye mahsus bir iş olmaktan çıkıp sürekli korunan bir standart haline gelir.
Boyut, Güvenlik ve Hız Kazanımını Ölçmek#
Optimizasyonun değerini görmek için önce ve sonra ölçüm yapmak gerekir. En temel komut, imaj boyutunu listelemektir:
docker images benim-uygulamam --format "{{.Repository}}:{{.Tag}} {{.Size}}"
Tipik bir Node.js API projesinde uyguladığımız adımların birikimli etkisini bir tabloda özetleyelim:
| Aşama | İmaj boyutu | Kazanım |
|---|---|---|
Başlangıç (node:20, tek aşama) | 1080 MB | — |
node:20-slim tabanına geçiş | 320 MB | %70 |
| Çok aşamalı derleme | 180 MB | %44 |
| RUN birleştirme + önbellek temizliği | 155 MB | %14 |
.dockerignore + prod bağımlılıklar | 140 MB | %10 |
Sonuç 1080 MB'dan 140 MB'a inmiş, yani yaklaşık yüzde 87 küçülme sağlanmıştır. Bu üç somut alanda geri döner. Hız: küçük imaj daha hızlı çekilir ve dağıtılır; yeni sürüm yayınlama süresi ölçülebilir biçimde kısalır, ölçeklenirken yeni konteynerler saniyeler içinde ayağa kalkar. Depolama ve bant genişliği: registry ve sunucu diskinde daha az yer kaplar, transfer maliyeti düşer. Güvenlik: her paket bir açık taşıyabileceği için, taban ne kadar küçükse potansiyel açık o kadar azdır.
Güvenlik kazanımını da ölçebilirsiniz. docker scout veya trivy gibi araçlar imajdaki bilinen açıkları tarar:
# Trivy ile açık taraması
trivy image benim-uygulamam:latest
# Docker Scout ile hızlı özet
docker scout quickview benim-uygulamam:latest
Ağır bir ubuntu tabanında yüzlerce bilinen açık raporlanırken, distroless veya iyi budanmış bir slim imajda bu sayı çoğu zaman tek haneye iner. Bu araçları CI boru hattınıza ekleyerek her yeni imajı otomatik taratmak, üretime savunmasız bir katman göndermenizi engeller. Kendi Docker tabanlı uygulamalarınızı hazır şablonlarla hızlıca ayağa kaldırmak isterseniz App Center üzerinden n8n, Ghost ve Nextcloud gibi servisleri optimize edilmiş imajlarla kurabilirsiniz.
Sıkça Sorulan Sorular#
Alpine imajı her zaman en iyi seçim mi?#
Hayır, Alpine her senaryoda en iyi seçim değildir. musl libc kullandığı için glibc'ye bağımlı bazı ikili paketlerde, özellikle Node yerel modüllerinde veya Python bilimsel kütüphanelerinde uyumsuzluk ve zor teşhis edilen çalışma zamanı hatalarıyla karşılaşabilirsiniz. Çoğu proje için slim varyantları daha güvenli bir denge sunar; boyut kazancı biraz daha az olsa da uyumluluk sorunları yaşamazsınız. Alpine'i tercih edecekseniz mutlaka kapsamlı test edin ve üretim davranışını doğrulayın.
Çok aşamalı derleme uygulama performansını etkiler mi?#
Çalışma zamanı performansını doğrudan etkilemez; nihai imaj yalnızca uygulamanızı ve gerçek bağımlılıklarını içerdiği için uygulama aynı hızda çalışır. Asıl kazanım dağıtım tarafındadır: daha küçük imaj daha hızlı çekilir ve daha çabuk başlatılır. Derleme süresi ilk seferde biraz artabilir çünkü iki ayrı aşama kurulur, ancak katman önbelleği sayesinde sonraki derlemeler genellikle daha hızlı tamamlanır.
Silinen dosyalar imajda neden kalıyor?#
Docker imajı katmanlı bir dosya sistemi kullanır ve her katman salt-okunurdur. Bir dosyayı sonraki bir katmanda sildiğinizde, üst katman o dosyayı görünmez yapan bir işaret ekler ama alttaki katmanda dosyanın fiziksel verisi durmaya devam eder. Bu yüzden kurulum ve temizliği aynı RUN komutunda zincirlemek gerekir; böylece dosya hiç kalıcı bir katmana yazılmaz ve gerçek boyut düşer.
İmaj boyutu güvenliği gerçekten etkiler mi?#
Evet, doğrudan etkiler. İmajınızdaki her ikili dosya, kütüphane ve araç potansiyel bir güvenlik açığı taşıyabilir; taban ne kadar büyükse saldırı yüzeyi o kadar geniştir. Kabuk ve paket yöneticisi içermeyen distroless imajlar, bir saldırgan konteynere sızsa bile elinde çalıştıracak araç bulamayacağı için sömürüyü ciddi biçimde zorlaştırır. trivy gibi tarayıcılarla ölçtüğünüzde küçük imajların çok daha az bilinen açık raporladığını göreceksiniz.
dive kullanmak zorunda mıyım?#
Zorunlu değil ama şiddetle önerilir. docker history benim-uygulamam komutuyla katman boyutlarını kabaca görebilirsiniz, ancak dive hangi katmanda tam olarak hangi dosyaların yer kapladığını ve ne kadarının boşa gittiğini görsel olarak gösterir. Özellikle beklenmedik biçimde şişen bir imajı teşhis ederken zamandan büyük tasarruf sağlar. CI boru hattınıza verimlilik eşiği olarak eklerseniz, gelecekteki şişmeleri de otomatik yakalarsınız.
Nihai imajı hangi kullanıcıyla çalıştırmalıyım?#
Konteyneri asla root ile çalıştırmayın. Dockerfile içinde USER node veya kendi oluşturduğunuz düşük yetkili bir kullanıcı tanımlayarak uygulamayı sınırlı haklarla başlatın. Bu, imaj boyutunu değiştirmez ama bir açık sömürüldüğünde saldırganın konteyner içinde yapabileceklerini kısıtlar. Küçük imaj ve düşük yetkili kullanıcı birlikte, güvenli bir üretim dağıtımının temel taşlarıdır.
Kapanış#
Docker imajlarını küçültmek, tek bir sihirli komutla değil, birkaç disiplinli alışkanlığın bir araya gelmesiyle olur: hafif ve sürümü sabitlenmiş bir temel imaj, çok aşamalı derleme, kurulum ile temizliği aynı katmanda toplama, titiz bir .dockerignore ve dive ile düzenli analiz. Bu adımları uyguladığınızda gigabaytlık imajların yüz megabaytın altına indiğini, dağıtımların hızlandığını ve güvenlik taramalarının çok daha temiz sonuç verdiğini göreceksiniz. En önemlisi, bu kazanımlar bir kere yapılıp unutulan değil, CI kapılarıyla sürekli korunan bir standart haline gelmelidir.
Bu optimizasyonların gerçek faydasını görmek için imajlarınızı çalıştıracağınız altyapının da yeterli ve esnek olması gerekir. Konteyner tabanlı projeleriniz için tam kök erişimi ve kaynak kontrolü sunan VDS sunucularımızı veya daha hafif iş yükleri için sanal sunucu çözümlerimizi inceleyebilir; sunucu kurulumu ve güvenlik yapılandırmasını bize bırakmak isterseniz yönetilen sunucu hizmetimizden yararlanabilirsiniz. Doğru boyutlanmış imajlar ve doğru sunucu bir araya geldiğinde, hem daha hızlı hem de daha güvenli bir üretim ortamına kavuşursunuz.