← Kursa Dön
📄 Text · 30 min

Image Boyut Optimizasyonu — Alpine, distroless, scratch

Bu bölümün son dersine geldik. Multi-stage build ile build araçlarını ayırdık, layer caching ile build süresini düşürdük, dil bazlı optimizasyon tekniklerini öğrendik. Bu derste son parçayı yerine koyacağız: base image seçimi, minimal image stratejileri ve güvenlik taraması.

Bir ev yapıyorsun diyelim. İnşaat bitti, boyandı, mobilyalar konuldu. Ama avluda hâlâ inşaat molozları duruyor. Bahçe düzenlenmemiş, gereksiz malzemeler toplanmamış. İç güzel ama dış bakımsız — hem kötü görünür hem de güvenlik açığı (birisi molozların arkasına saklanabilir). Docker image'ın da öyle: çalışıyor ama içinde gereksiz paketler, shell, debug araçları varsa — hem büyük hem de saldırı yüzeyi geniş.


Base Image Seçimi — Her Şeyin Temeli

Base image seçimi, image boyutunu ve güvenliğini belirleyen en temel karar. Aynı uygulamanın farklı base image'larla boyutu:

Base Image                      Boyut     Güvenlik   Debug
────────────────────────────────────────────────────────────
ubuntu:22.04                    77 MB     Orta       Kolay
debian:bookworm                 124 MB    Orta       Kolay
debian:bookworm-slim            74 MB     İyi        Orta
alpine:3.19                     7 MB      İyi        Zor
distroless/base-debian12        20 MB     Çok iyi    Çok zor
scratch                         0 MB      Mükemmel   İmkansız

Debian Slim

"Slim" varyantlar, full Debian'dan gereksiz dosyaları çıkarır (man pages, docs, bazı kütüphaneler):

FROM python:3.12-slim
# python:3.12       → 1.0 GB
# python:3.12-slim  → 130 MB  (7x daha küçük!)

Çoğu proje için slim en iyi denge noktasıdır. Çoğu paket sorunsuz çalışır, boyut makul.

Alpine

Alpine Linux musl libc + BusyBox kullanır — çok kompakt ama bazı uyumsuzluklar olabilir:

FROM node:20-alpine
# node:20        → 1.1 GB
# node:20-alpine → 130 MB  (8x daha küçük!)

Alpine ile karşılaşabileceğin sorunlar:

musl vs glibc uyumsuzluğu: Bazı native binary'ler (özellikle pre-built olanlar) glibc bekler ama Alpine musl kullanır. bcrypt, sharp, grpc gibi paketlerde sorun olabilir:

# Hata: Error loading shared library: libc.musl-x86_64.so

Çözüm: bu paketleri Alpine'da source'dan compile et veya alpine uyumlu versiyonunu kullan:

FROM node:20-alpine
RUN apk add --no-cache python3 make g++  # Native build araçları
COPY package*.json ./
RUN npm ci

DNS çözümleme farklılığı: Alpine'ın DNS resolver'ı bazı kurumsal DNS yapılandırmalarında sorun çıkarabilir.

Alpine ne zaman kullanmalı? Basit Go/Rust binary'ler ve sorunsuz çalıştığını test ettiğin projeler için.

Google Distroless

Distroless image'lar Google tarafından üretilir. İçinde sadece uygulamanın çalışması için gereken minimum bileşenler var — shell yok, paket yöneticisi yok:

# Go için
FROM gcr.io/distroless/static-debian12
# 2 MB — statik binary'ler için

# Java için
FROM gcr.io/distroless/java21-debian12
# ~200 MB — JRE dahil

# Node.js için
FROM gcr.io/distroless/nodejs20-debian12
# ~130 MB — Node.js runtime dahil

# Python için
FROM gcr.io/distroless/python3-debian12
# ~50 MB — Python runtime dahil

Shell olmadığı için docker exec -it container sh çalışmaz! Debug etmek için "debug" varyantını kullan:

# Production
FROM gcr.io/distroless/static-debian12

# Debug (shell dahil — sadece development/debugging için)
FROM gcr.io/distroless/static-debian12:debug

scratch

Tamamen boş image. İçinde hiçbir şey yok — dosya sistemi bile yok. Sadece statik binary'ler için (Go, Rust):

FROM scratch
COPY --from=builder /myapp /myapp
ENTRYPOINT ["/myapp"]

Karar Matrisi

Senaryo                              Önerilen Base Image
────────────────────────────────────────────────────────
Go/Rust statik binary                scratch veya distroless/static
Java Spring Boot                     eclipse-temurin:XX-jre-alpine
Node.js production                   node:XX-alpine veya distroless
Python production                    python:3.XX-slim
Genel amaçlı, debug kolaylığı       debian:bookworm-slim
Maximum güvenlik, minimum boyut      distroless

Gereksiz Dosyaları Temizle

Image'da kalmaması gereken şeyleri temizlemek boyutu önemli ölçüde düşürür.

apt Cache Temizliği

# ❌ KÖTÜ — cache kalıyor
RUN apt-get update
RUN apt-get install -y curl
# apt cache image'da kalır — 50-100MB boşa gider

# ✅ İYİ — tek layer'da temizle
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

--no-install-recommends önerilen ama gerekli olmayan paketleri yüklemez — ciddi boyut tasarrufu.

apk (Alpine) Temizliği

RUN apk add --no-cache curl wget
# --no-cache: index cache'i tutmaz

pip Cache

RUN pip install --no-cache-dir -r requirements.txt

npm Cache

RUN npm ci && npm cache clean --force
# Veya daha iyisi: multi-stage build ile production node_modules ayrı

Temp Dosyaları

# Build sonrası gereksiz dosyaları sil
RUN npm run build && \
    rm -rf src/ test/ .eslintrc .prettierrc tsconfig.json && \
    npm prune --production

Non-Root User — Güvenlik Temeli

Container'lar varsayılan olarak root kullanıcıyla çalışır. Bu büyük bir güvenlik riski — container'dan kaçılırsa (container escape) host'ta root yetkisi elde edilebilir:

# ❌ KÖTÜ — root olarak çalışıyor
FROM node:20-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]
# ✅ İYİ — non-root user
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["node", "server.js"]

Bazı resmi image'lar zaten non-root user tanımlar:

# Node resmi image'ında "node" user'ı var
FROM node:20-alpine
USER node

# PostgreSQL'de "postgres" user'ı var
FROM postgres:16-alpine
# Zaten postgres olarak çalışır

Volume izin sorunu çıkarsa (permission denied):

RUN mkdir -p /app/data && chown appuser:appgroup /app/data

.dockerignore Optimizasyonu

Kapsamlı bir .dockerignore hem build hızını hem de güvenliği artırır:

# .dockerignore
# Version control
.git
.gitignore

# Dependencies (container'da yeniden yüklenecek)
node_modules
vendor
__pycache__
*.pyc
.venv

# Build artifacts
dist
build
target
*.jar

# IDE
.vscode
.idea
*.swp
*.swo

# Docker
Dockerfile*
docker-compose*
.dockerignore

# Docs & tests
*.md
LICENSE
docs
test
tests
coverage
.nyc_output

# Environment & secrets
.env
.env.*
*.pem
*.key

Güvenlik açısından en kritik satırlar: .env, *.pem, *.key — bunlar asla image'a girmemeli!


Güvenlik Taraması — Vulnerabilities

Image'ını build ettin, boyutu küçülttün. Ama içinde bilinen güvenlik açıkları olabilir — eski bir OpenSSL, zafiyetli bir kütüphane. Bunları taramak zorunlu.

Docker Scout

# Image'ı tara
docker scout cves myapp:latest

# Öneriler
docker scout recommendations myapp:latest

Trivy (Aqua Security)

# Kurulum
docker pull aquasec/trivy

# Image tara
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy image myapp:latest

# Sadece HIGH ve CRITICAL
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy image --severity HIGH,CRITICAL myapp:latest

Çıktı:

myapp:latest (alpine 3.19)
Total: 2 (HIGH: 1, CRITICAL: 1)

┌──────────────┬────────────────┬──────────┬───────────────────┐
│   Library    │ Vulnerability  │ Severity │  Fixed Version    │
├──────────────┼────────────────┼──────────┼───────────────────┤
│ libcrypto3   │ CVE-2024-XXXX  │ CRITICAL │ 3.1.4-r5          │
│ curl         │ CVE-2024-YYYY  │ HIGH     │ 8.5.0-r1          │
└──────────────┴────────────────┴──────────┴───────────────────┘

CI/CD'de Otomatik Tarama

# GitHub Actions
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: myapp:latest
    format: 'table'
    exit-code: '1'             # CRITICAL varsa pipeline fail
    severity: 'CRITICAL,HIGH'

exit-code: '1' sayesinde CRITICAL güvenlik açığı bulunursa deploy engellenir.


Image Metadata — Labels

Image'a metadata eklemek yönetim kolaylığı sağlar:

LABEL maintainer="team@example.com"
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.version="1.2.3"
LABEL org.opencontainers.image.description="Blog Platform API"
docker inspect myapp:latest --format='{{json .Config.Labels}}' | jq

Tam Optimizasyon Checklist

Şimdi bu bölümde öğrendiğimiz her şeyi bir checklist'te toplayalım:

☐ Multi-stage build kullan — build araçları final image'da olmasın
☐ Minimal base image seç — alpine, slim veya distroless
☐ Non-root user ile çalıştır
☐ .dockerignore kapsamlı olsun — .git, node_modules, .env
☐ Layer cache'i optimize et — dependency dosyalarını önce kopyala
☐ RUN komutlarını birleştir ve cache temizle
☐ BuildKit cache mount kullan
☐ Gereksiz dosyaları sil — docs, test, cache
☐ Güvenlik taraması yap — Trivy veya Docker Scout
☐ HEALTHCHECK tanımla
☐ Resource limitleri koy (Compose'da)
☐ Labels ekle — maintainer, version, source

Gerçek Dünya Optimizasyon Örneği

Optimizasyon öncesi ve sonrasını karşılaştıralım:

# ❌ ÖNCE — 1.2 GB, root user, güvenlik açıkları
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/server.js"]
# ✅ SONRA — 150 MB, non-root, güvenli, cache-friendly
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

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

FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
USER nonroot
EXPOSE 3000
CMD ["dist/server.js"]
Metrik                  Önce          Sonra
─────────────────────────────────────────────
Image boyutu            1.2 GB        150 MB
Build süresi (ilk)      4 dakika      3 dakika
Build süresi (cache)    4 dakika      20 saniye
Güvenlik açığı          47            3
Root çalışma            Evet          Hayır
Shell erişimi           Evet          Hayır

Image boyutu %88 küçüldü, cache'li build 12x hızlandı, güvenlik açıkları %94 azaldı. Ve bunu yapmak için sadece Dockerfile'ı değiştirmemiz yetti — uygulama koduna dokunmadık.


Bu Derste Ne Öğrendik?

  • Base image seçimi her şeyin temeli — alpine (7MB), slim (74MB), distroless (20MB), scratch (0MB).

  • Distroless image'lar shell içermez — saldırı yüzeyi minimum ama debug zor.

  • Alpine'ın musl/glibc uyumsuzluğuna dikkat — test et.

  • Non-root user ile çalıştır — güvenlik açısından zorunlu.

  • Cache'leri temizle: rm -rf /var/lib/apt/lists/*, --no-cache-dir, npm cache clean.

  • Güvenlik taraması (Trivy, Docker Scout) CI/CD pipeline'ına entegre et.

  • Tam optimizasyon 1.2GB → 150MB, 47 CVE → 3 CVE düşüş sağlayabilir.

Bu, Docker image optimizasyonu bölümünün son dersiydi. Artık küçük, hızlı ve güvenli Docker image'ları oluşturmayı biliyorsun. Tebrikler — Docker'ın temellerinden ileri seviye optimizasyona kadar uzun bir yol kat ettin!