aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Araçlar & DevOps

Dockerfile Yazma: Katman ve Cache ile Verimli İmaj

İyi yazılmış bir Dockerfile, uygulamanın her yerde aynı şekilde çalışmasını sağlayan tarifin kalbidir. Ama çoğu Dockerfile gereğinden büyük imajlar üretir ve her küçük değişiklikte baştan derler. Bu rehberde Docker'ın katman (layer) ve cache mantığını anlayarak; daha küçük, daha hızlı ve tekrar üretilebilir imajlar oluşturmayı adım adım göreceğiz.

Katman mantığı: her komut bir katmandır

Bir imaj, üst üste binmiş salt-okunur katmanlardan oluşur. Dockerfile'daki FROM, RUN, COPY ve ADD gibi talimatların her biri yeni bir katman üretir. Docker bu katmanları önbelleğe alır: bir talimat ve girdileri değişmediyse, Docker o katmanı yeniden hesaplamaz, hazır olanı kullanır.

Kritik nokta şudur: bir katman değişince, altındaki tüm katmanların cache'i de geçersiz olur. Yani Dockerfile'da sık değişen şeyleri sona, nadiren değişenleri başa koymak build süresini doğrudan etkiler. Sıra, bu işin yarısıdır.

Basit bir Dockerfile ile başlayalım

Bir Node.js uygulaması için tipik ama kötü bir örnek şöyle görünür:

FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]

Bu çalışır, ama bir sorun var: COPY . . tüm projeyi kopyaladığı için, kaynak kodda tek bir satır değiştirdiğinde altındaki RUN npm install katmanının cache'i bozulur ve bağımlılıklar her seferinde baştan kurulur. Halbuki package.json değişmediyse bağımlılıkların değişmesi gerekmez.

Cache'i doğru kullanmak: bağımlılıkları ayır

Çözüm, önce yalnızca bağımlılık tanımlarını kopyalamak ve kurulumu yapmak; uygulama kodunu en sona bırakmaktır:

FROM node:20
WORKDIR /app

# Önce sadece bağımlılık dosyaları
COPY package.json package-lock.json ./
RUN npm ci

# Sonra uygulama kodu
COPY . .

CMD ["node", "server.js"]

Artık yalnızca kaynak kodu değiştiğinde son COPY . . katmanı yeniden çalışır; npm ci katmanı cache'ten gelir. package-lock.json değişmediği sürece bağımlılıklar yeniden kurulmaz. Bu küçük sıralama farkı, build süresini dakikalardan saniyelere indirebilir.

npm install yerine npm ci kullanmak da bilinçli bir tercihtir: ci, lock dosyasına birebir uyar, deterministiktir ve CI ortamları için daha güvenilirdir.

.dockerignore ile gereksiz dosyaları dışarıda bırak

COPY . . her şeyi kopyalar — node_modules, .git, log dosyaları, yerel .env... Bunlar hem imajı şişirir hem de cache'i gereksiz yere bozar. Proje köküne bir .dockerignore dosyası ekleyin:

node_modules
.git
.gitignore
Dockerfile
*.log
.env
dist
coverage

Bu dosya, .gitignore ile aynı mantıkla çalışır ve build context'e (Docker daemon'a gönderilen dosyalar) girmeyecek yolları belirtir. Daha küçük context, daha hızlı build ve daha temiz bir imaj demektir.

Multi-stage build: derleme araçlarını imajdan atın

Bir uygulamayı derlemek için gereken araçlar (derleyiciler, dev bağımlılıkları), uygulamayı çalıştırmak için gerekli değildir. Multi-stage build, derlemeyi bir aşamada yapıp yalnızca sonucu küçük bir son imaja taşımanızı sağlar:

FROM node:20 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Yalnızca üretim çıktısını taşıyan ince son aşama
FROM node:20-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]

Burada --from=build ile yalnızca derlenmiş dist klasörünü ikinci aşamaya kopyalıyoruz. Son imaj, derleme araçlarını ve dev bağımlılıklarını içermez; bu da çoğu zaman imaj boyutunu yarı yarıya, hatta daha fazla düşürür. node:20-slim gibi ince bir taban imaj seçmek de boyutu ve saldırı yüzeyini azaltır.

Pratik kurallar ve sık yapılan hatalar

  • RUN komutlarını birleştirin: Her RUN bir katmandır. Paket kurulumlarını && ile birleştirip aynı katmanda temizlik yapın; örneğin Debian tabanında apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*.
  • Sürümleri sabitleyin: FROM node:20, FROM node:latest'ten iyidir; tekrar üretilebilirlik için belirli bir sürüm kullanın.
  • CMD ve ENTRYPOINT'i exec formuyla yazın: CMD ["node", "server.js"] shell formuna göre sinyalleri (SIGTERM) doğru iletir ve düzgün kapanma sağlar.
  • Root olarak çalıştırmayın: Üretimde bir USER tanımlayarak konteyneri ayrıcalıksız bir kullanıcıyla çalıştırın.
  • WORKDIR kullanın: cd yerine WORKDIR; bu hem okunabilir hem de katmanlar arasında kalıcıdır.

İmajı oluşturma ve denetleme

Dockerfile hazır olduğunda imajı oluşturmak ve boyutunu görmek basittir:

docker build -t benim-uygulamam:1.0 .
docker images benim-uygulamam
docker history benim-uygulamam:1.0

docker history, hangi talimatın ne kadar yer kapladığını katman katman gösterir; bu, şişen yerleri bulmak için en pratik araçtır. BuildKit (modern Docker'da varsayılan) ile build çıktısında hangi adımların CACHED olduğunu da görebilirsiniz.

Sık Sorulan Sorular

Dockerfile mi Compose mu kullanmalıyım?

İkisi farklı işler yapar. Dockerfile tek bir imajın nasıl oluşturulacağını tanımlar; docker compose ise birden fazla konteyneri (uygulama + veritabanı gibi) birlikte tanımlayıp çalıştırır. Compose, çoğunlukla Dockerfile'ınızı build: ile referans alır — yani biri diğerinin yerine geçmez, birlikte kullanılırlar.

COPY ile ADD arasındaki fark nedir?

İkisi de dosya kopyalar, ama ADD'in iki ekstra yeteneği vardır: uzak URL'lerden indirme ve yerel tar arşivlerini otomatik açma. Bu davranışlar sürpriz yaratabildiği için, basit dosya kopyalamada her zaman COPY tercih edin; ADD'i yalnızca tar açma gibi özel ihtiyaçlar için kullanın.

İmajım neden hâlâ çok büyük?

En sık nedenler: ince olmayan bir taban imaj (slim veya alpine deneyin), multi-stage build kullanmamak, aynı katmanda temizlik yapmamak ve .dockerignore eksikliği. docker history ile katmanları inceleyip en büyük katmanı hedefleyin.

Verimli, küçük ve hızlı bir imaja mı ihtiyacınız var? Mevcut Dockerfile'larınızı katman ve cache açısından optimize edip CI/CD süreçlerinizi hızlandırabilirim — benimle iletişime geçin.

Devamı için