Dil Bazlı Optimizasyon — Java, Node.js, Go, Python
Bir önceki derste multi-stage build'in temel mantığını öğrendik. Ama her programlama dilinin Docker image'ı optimize etmek için kendine özgü hileleri var. Node.js'in node_modules sorunu Java'dan çok farklı, Go'nun statik binary avantajı Python'da yok.
Farklı ülkelere kargo gönderiyorsun diyelim. Japonya'ya kompakt paketleme lazım. ABD'ye standart kutu yeter. Her ülkenin kuralı farklı — tek tip paketleme çalışmaz. Docker image'ları da öyle: her dilin Dockerfile'ı farklı optimize edilmeli.
Node.js Optimizasyonu
Node.js'in en büyük düşmanı: node_modules. Binlerce dosya, yüzlerce megabyte. Bu canavarı doğru yönetmek image boyutunu %70+ düşürür.
Production-Ready Node.js Dockerfile
# === Stage 1: Dependencies ===
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
# === Stage 2: Build (TypeScript varsa) ===
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# === Stage 3: Production ===
FROM node:20-alpine AS production
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
USER app
EXPOSE 3000
CMD ["node", "dist/server.js"]Üç ayrı stage kullandık ve her birinin amacı farklı. İlk stage sadece production dependencies yüklüyor — devDependencies yok. İkinci stage hem dev hem prod dependencies ile build yapıyor — TypeScript compile etmek için TypeScript compiler lazım. Üçüncü stage'de ise birinci stage'den production node_modules'ü, ikinci stage'den compile edilmiş kodu alıyoruz.
Node.js Spesifik İpuçları
`npm ci` vs `npm install`: Her zaman npm ci kullan. Bu komut package-lock.json'dan birebir yükler — deterministic ve daha hızlı. npm install ise lock dosyasını güncelleyebilir.
`--only=production`: devDependencies (TypeScript, test framework'ler, linter'lar) olmadan yükler. Boyut farkı dramatik — 400MB vs 120MB.
BuildKit cache mount: npm'in indirme cache'ini build'ler arası korur:
RUN --mount=type=cache,target=/root/.npm \
npm ci --only=productionİlk build'de paketler indirilir ve cache'e yazılır. Sonraki build'lerde package.json değişse bile paketler cache'ten gelir — network indirmesi yok, çok daha hızlı.
Signal handling — tini: Node.js PID 1 olarak çalıştığında SIGTERM gibi sinyalleri doğru handle edemeyebilir. tini bu sorunu çözer:
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]Next.js standalone output: Next.js projelerinde next.config.js'e output: 'standalone' ekle. Bu, sadece gerekli dosyaları içeren minimal bir output üretir — tüm node_modules yerine sadece kullanılan modüller:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]Boyut: ~100MB (tüm node_modules yerine sadece gerekli modüller).
Java Optimizasyonu
Java'nın iki büyük sorunu var. Birincisi JDK vs JRE — build araçları (JDK, Maven/Gradle) runtime'da gereksiz. İkincisi fat JAR — her şey tek dosyada ama layer cache'i çalışmaz.
JDK vs JRE Farkı
# Boyut karşılaştırması
eclipse-temurin:21 # JDK — 360MB (build için)
eclipse-temurin:21-jre # JRE — 250MB (runtime için)
eclipse-temurin:21-jre-alpine # JRE Alpine — 130MB 🏆Production'da JDK'ya ihtiyacın yok — sadece JRE yeterli. Bu tek başına 200MB+ tasarruf.
JVM Memory Tuning
Container environment'ta JVM'in bellek ayarları kritik:
ENV JAVA_OPTS="-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:InitialRAMPercentage=50.0 \
-XX:+UseG1GC"
CMD ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]-XX:+UseContainerSupport JVM'e "sen container'dasın, container'ın bellek limitini oku" der. Yoksa JVM host'un tüm RAM'ini görüp aşırı bellek ayırabilir — OOM Kill!
jlink ile Custom JRE
Uygulamanın sadece belirli Java modüllerini kullanıyorsa, gereksiz modülleri çıkararak çok daha küçük bir JRE oluşturabilirsin:
FROM eclipse-temurin:21 AS jre-builder
RUN jlink \
--add-modules java.base,java.logging,java.sql,java.naming \
--strip-debug --no-man-pages --no-header-files \
--compress=zip-6 --output /custom-jre
FROM alpine:3.19
COPY --from=jre-builder /custom-jre /opt/java
ENV PATH="/opt/java/bin:$PATH"
COPY --from=builder /app/target/app.jar /app/app.jar
CMD ["java", "-jar", "/app/app.jar"]Custom JRE: ~50MB (full JRE: ~200MB).
Maven/Gradle Cache
# Maven cache mount
RUN --mount=type=cache,target=/root/.m2/repository \
mvn package -DskipTests -B
# Gradle cache mount
RUN --mount=type=cache,target=/root/.gradle \
./gradlew bootJar --no-daemon -x testGo Optimizasyonu
Go, Docker optimizasyonunun şampiyonu. Statik binary oluşturur, hiçbir runtime bağımlılığı yok. scratch image ile 5-15MB mümkün.
Production-Ready Go Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s -X main.version=1.0.0" \
-o /server ./cmd/server
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /server /server
EXPOSE 8080
ENTRYPOINT ["/server"]Go Spesifik İpuçları
`CGO_ENABLED=0`: C bağımlılığı olmayan statik binary oluşturur. scratch image'da çalışması için zorunlu.
`-ldflags="-w -s"`: -w DWARF debug bilgisini çıkarır, -s symbol table'ı çıkarır. Binary boyutunu ~%30 küçültür.
Build bilgisi gömme:
RUN go build -ldflags="-w -s \
-X main.version=$(git describe --tags) \
-X main.buildDate=$(date -u +%Y%m%dT%H%M%S)" \
-o /server .scratch vs distroless vs alpine:
| Image | Boyut | Shell | Debug |
|---|---|---|---|
| scratch | 0MB | Yok | Çok zor |
| distroless/static | 2MB | Yok | Zor |
| alpine | 7MB | Var | Kolay |
Development'ta alpine, production'da scratch veya distroless kullan.
Go module cache:
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o /server .İki cache mount: biri indirilen modüller için, biri compile cache için. Build süresini dramatik düşürür.
Python Optimizasyonu
Python'un zorlukları: C extension'lı paketler (numpy, psycopg2) build araçları ister, pip cache büyük olabilir, virtual environment karmaşası var.
Production-Ready Python Dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc libpq-dev && \
rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.12-slim AS production
RUN apt-get update && \
apt-get install -y --no-install-recommends libpq5 && \
rm -rf /var/lib/apt/lists/*
RUN useradd --create-home appuser
WORKDIR /app
COPY --from=builder /root/.local /home/appuser/.local
COPY . .
USER appuser
ENV PATH=/home/appuser/.local/bin:$PATH
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "-w", "4", "app:app"]Python Spesifik İpuçları
`--no-cache-dir`: pip cache'ini image'da bırakma — gereksiz yer kaplar.
`PYTHONDONTWRITEBYTECODE=1`: .pyc dosyaları oluşturmaz — image boyutunu azaltır.
`PYTHONUNBUFFERED=1`: stdout/stderr buffer'lamaz — Docker loglarında çıktıyı anında görürsün.
Virtual environment yaklaşımı (alternatif):
FROM python:3.12-slim AS builder
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY . .
CMD ["python", "app.py"]Tüm venv dizinini kopyalamak, --user install'dan daha temiz ve taşınabilir.
Poetry kullanıyorsan:
FROM python:3.12-slim AS builder
RUN pip install poetry
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.in-project true && \
poetry install --only main --no-interactionpip cache mount:
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txtRust Optimizasyonu (Bonus)
Rust, Go gibi statik binary üretir ama compile süresi çok daha uzun. Cache stratejisi kritik:
FROM rust:1.77-alpine AS builder
RUN apk add --no-cache musl-dev
WORKDIR /app
# Dependency cache trick — boş main ile sadece bağımlılıkları compile et
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release
# Gerçek kaynak kodu
RUN rm -rf src
COPY src ./src
RUN touch src/main.rs && cargo build --release
FROM scratch
COPY --from=builder /app/target/release/myapp /myapp
ENTRYPOINT ["/myapp"]Boyut: 3-8MB. strip ile daha da küçültülebilir.
Boyut Karşılaştırma Tablosu
┌──────────────────────────────────────────────────────────────┐
│ Aynı Hello World API — Dil Karşılaştırması │
├──────────────┬──────────────┬──────────────┬────────────────┤
│ Dil │ Naive Build │ Optimized │ Ultra-Optimized│
├──────────────┼──────────────┼──────────────┼────────────────┤
│ Node.js │ 1.1 GB │ 180 MB │ 130 MB (distr) │
│ Java │ 700 MB │ 250 MB │ 130 MB (jlink) │
│ Python │ 1.0 GB │ 200 MB │ 150 MB (slim) │
│ Go │ 850 MB │ 15 MB │ 8 MB (scratch) │
│ Rust │ 1.5 GB │ 12 MB │ 5 MB (scratch) │
└──────────────┴──────────────┴──────────────┴────────────────┘Multi-Platform Build
Aynı Dockerfile'dan farklı CPU mimarileri (AMD64, ARM64) için build:
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .Go'da platform-aware build:
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /server .Bu Derste Ne Öğrendik?
Node.js:
npm ci --only=production+ alpine + tini → 180MB.Java: JRE-alpine + layered JAR + jlink custom JRE → 130-250MB.
Go:
CGO_ENABLED=0+ldflags="-w -s"+ scratch → 8-15MB.Python:
--userinstall + slim +PYTHONDONTWRITEBYTECODE→ 150-200MB.BuildKit cache mount tüm dillerde build süresini dramatik düşürür.
Her dilin dependency cache pattern'ı farklı — go.mod, package.json, pom.xml, requirements.txt'i önce kopyala.
Sonraki derste layer caching stratejilerini derinlemesine inceleyeceğiz — build süresini 10 dakikadan 30 saniyeye düşürmenin yolları.
AI Asistan
Sorularını yanıtlamaya hazır