İçeriğe geç
Can Uğurlu
Geri dön

Docker imajınız neden 1,2 GB

Docker imajınız 1,2 GB’a çıktıysa sebep neredeyse her zaman tek şey: build araçları runtime imajına da giriyor. node_modules, derleyici, apt paketleri: hepsi tek katmanda, hepsi tek imajda taşınıyor.

Çözüm multi-stage build. Derleyen imaj ayrı, çalışan imaj ayrı.

Tek aşamalı dockerfile neden şişer

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

Bu dockerfile’ın son katmanında derleme sırasında kurulan her şey duruyor: npm install’ın indirdiği devDependencies, derleyici, .git klasörü kopyalandıysa o da. Runtime’da hiçbirine ihtiyaç yok, ama imajın içinde kalıyorlar.

Runtime aşaması sadece şunu içermeli: çalıştırılabilir çıktı, o çıktının ihtiyaç duyduğu dependencies, ve bir çalışma zamanı. Onu inşa eden derleyici, devDependencies ve apt cache’i içermemeli.

Multi-stage build

FROM node:20 AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM node:20-slim AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
CMD ["node", "dist/server.js"]

builder aşaması npm install -g pnpm, derleyici ve kaynak kodun tamamını içerir, final imaja hiç girmez. runtime aşaması sadece COPY --from=builder ile taşınanları alır. Docker, kullanılmayan aşamaları final imaja koymuyor.

RUN apt-get install sonra RUN rm katmanı küçültmüyor

RUN apt-get update && apt-get install -y build-essential
RUN npm run build
RUN apt-get remove -y build-essential

Bu üç satır üç ayrı katman üretiyor. build-essential ilk katmanda kalıcı olarak yazılıyor. Üçüncü katman onu dosya sisteminden siliyor ama katman birleşiminde önceki katman hâlâ orada. Docker’ın katmanlı dosya sistemi eklemeli çalışır, geriye silme yapmaz. İmaj boyutu küçülmez, büyür; çünkü şimdi hem kurulum hem silme katmanı var.

Aynı işi tek RUN içinde yapmak da yardımcı olmaz, çünkü sorun katman sayısı değil, o araçların runtime imajına hiç girmemesi gerektiği. Doğru çözüm build araçlarını ayrı aşamaya koymak, sonra silmeye çalışmak değil.

alpine mi slim mi

node:20-alpine node:20-slim’den küçük, çünkü musl libc kullanıyor ve paket seti minimal. Ama alpine bazı native modüllerle (özellikle node-gyp gerektiren paketler) uyumsuzluk çıkarabiliyor, çünkü bu paketler glibc bekliyor.

PHP tarafında da aynı seçim var: php:8.3-fpm-alpine küçük ama bazı eklentiler (intl, gd) apk üzerinden ayrı kurulum istiyor.

Kural basit: native bağımlılığı olmayan bir Node uygulaması için alpine tercih edilir. Bir native modül alpine altında derlenmiyorsa slim kullanılır. O modülü uğraşarak alpine’a uydurmak, kazanılan birkaç yüz MB’a değmez.

Katman sırası cache’i belirler

COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .

Kaynak kod önce kopyalanırsa, koddaki her değişiklik pnpm install katmanını da geçersiz kılar. package.json ve lock dosyası önce kopyalanırsa, sadece bağımlılıklar değiştiğinde install katmanı yeniden çalışır. Kaynak kod değişikliği bağımlılık katmanına dokunmaz.

Bu sıra tek başına derleme süresini dakikalar mertebesinde kısaltabiliyor, özellikle CI’da her push’ta çalışan build’lerde.

.dockerignore şart

node_modules
.git
dist
.env
*.md

.dockerignore yoksa COPY . . yerel node_modules klasörünü de build context’ine sokuyor. Bu hem context’i büyütüyor hem de yerelde kurulu platforma özgü native modüllerin container’a taşınmasına yol açabiliyor. .git klasörü de aynı şekilde gereksiz yere context’e giriyor, bazı repolarda tek başına yüzlerce MB tutuyor.

Özet

Build araçlarını ayrı aşamaya koyun, runtime aşamasına sadece çıktıyı ve gerçek dependencies’i kopyalayın. Lock dosyasını kaynak koddan önce kopyalayın. .dockerignore yazın. Kurup silmek yerine hiç kurmayın.


Bu yazıyı paylaş:

Önceki Yazı
failed_jobs tablosuna bakmıyorsanız kuyruğunuz yok
Sonraki Yazı
Liquid'de döngü maliyeti