← Kursa Dön
📄 Text · 35 min

Image Temelleri — Base Image Seçimi ve Tag Stratejileri

Bu bölümün son dersine geldik. Şimdiye kadar image'ın ne olduğunu, nasıl yönetileceğini ve registry'leri öğrendik. Şimdi image oluşturmanın temellerini — base image seçimini, tag stratejilerini ve boyut optimizasyonunu — konuşacağız. Bu konular, professional Docker kullanımının yapı taşları.

Bir ev inşa ediyorsun diyelim. Temelden mi başlarsın, yoksa yarı mamul bir yapı üzerine mi eklersin? Eğer sadece bir oda ekliyorsan, sıfırdan temel atmak saçmalık — mevcut yapının üstüne çıkarsın. Docker image'larında da aynı mantık geçerli. Base image seçimi evin temeli gibidir — üstüne ne inşa edersen et, temel sağlam olmalı.


Base Image Nedir?

Her Docker image bir "base image" üzerine inşa edilir. Dockerfile'daki FROM satırı bu temeli belirler. Image'lar bir hiyerarşi oluşturur:

scratch (tamamen boş — 0 byte)
  └── alpine:3.19 (7MB — minimal Linux)
       └── node:20-alpine (130MB — Alpine + Node.js)
            └── myapp:v1 (150MB — Node.js + uygulama kodu)

scratch tamamen boş bir image — içinde hiçbir şey yok. Alpine onun üstüne minimal bir Linux kuruyor. Node.js Alpine'ın üstüne kurulmuş. Ve senin uygulamanın Node.js'in üstüne. Her katman bir öncekinin üzerine ekleniyor.


Varyantları Tanıyalım — Full, Slim, Alpine

Çoğu resmi image birden fazla varyantla sunuluyor. Her birinin farklı kullanım amacı var ve doğru olanı seçmek önemli.

Full (Default) — Geliştirme İçin

docker pull python:3.12      # ~1.02GB

İçinde Debian Bookworm'un tam versiyonu var: gcc, make, wget, curl, git, build-essential ve daha birçok araç. Bir şeyi build etmen gerektiğinde (C extension'lar, native modüller) bu image işini görür. Geliştirme ortamında debug yaparken de tüm araçlar elinin altında olur.

Ama boyutu büyük — 1GB'ın üzerinde. Production'da bu kadar şeye ihtiyacın yok.

Slim — Production İçin En İyi Tercih

docker pull python:3.12-slim  # ~150MB

Debian'ın minimal versiyonu. Build tools yok ama Python'un (veya Node.js'in, Java'nın) çalışması için gereken minimum her şey var. Boyut full'ün altıda biri.

Çoğu production senaryosunda slim en iyi tercih. Yeterince küçük, yeterince uyumlu. C extension'lar zaten pip'ten pre-built olarak geliyorsa, slim yeterli.

Alpine — Minimum Boyut

docker pull python:3.12-alpine  # ~51MB

Alpine Linux tabanlı — sadece 7MB'lık bir Linux dağıtımı üzerine kurulu. Boyut açısından harika ama bir uyarı var: Alpine, standart glibc yerine musl libc kullanıyor. Bu bazı C kütüphaneleriyle uyumluluk sorunlarına yol açabiliyor.

# Bu çalışmayabilir!
docker run --rm python:3.12-alpine pip install pandas
# ERROR: Could not build wheels for numpy

numpy gibi C extension'ları Alpine'da sorun çıkarabiliyor. Çözüm: ya build bağımlılıklarını kur (ama o zaman image büyür ve build süresi uzar) ya da slim kullan.

Tavsiyem: Alpine'ı körü körüne tercih etme. Uygulamanı test et — çalışıyorsa harika, sorun çıkarıyorsa slim'e geç.

Distroless — Maximum Güvenlik

Google'ın geliştirdiği distroless image'lar, shell bile olmayan ultra-minimal image'lar:

FROM gcr.io/distroless/nodejs20-debian12

İçinde shell yok, package manager yok. docker exec bash yapamazsın — çünkü bash yok. Bu da saldırı yüzeyini minimuma indirir. Ama debug çok zor.

Scratch — Tamamen Boş

FROM scratch
COPY mybinary /mybinary
CMD ["/mybinary"]

Hiçbir şey yok — 0 byte. Sadece statik derlenmiş binary'ler için kullanılır (Go, Rust). Son image sadece binary kadar yer kaplar (~5-20MB).

Karar Matrisi

Hangi varyantı ne zaman kullanmalısın? Şöyle düşün:

Geliştirme ve debug için full image kullan. Tüm araçlar hazır. Production'da çoğu uygulama için slim kullan. Yeterli ve güvenilir. Boyut kritik, basit uygulamalar için Alpine kullan. Ama uyumluluk testi yap. Maximum güvenlik için distroless kullan. Debug'dan vazgeç, güvenliği seç. Statik binary'ler (Go, Rust) için scratch kullan. Minimum boyut.


Dil/Framework Bazlı Rehber

Node.js

# node:20           1.1GB  — Geliştirme
# node:20-slim      220MB  — Production (önerilen)
# node:20-alpine    130MB  — Boyut kritikse

Production Node.js Dockerfile'ı şöyle görünür:

FROM node:20-slim
RUN apt-get update && apt-get install -y --no-install-recommends dumb-init && \
    rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production && npm cache clean --force
COPY . .
USER node
EXPOSE 3000
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]

Python

# python:3.12        1.02GB — C extension build gerekiyorsa
# python:3.12-slim   150MB  — Çoğu proje için (önerilen)
# python:3.12-alpine 51MB   — Dikkatli kullan

Java

# eclipse-temurin:21-jdk    ~350MB — Build için
# eclipse-temurin:21-jre    ~250MB — Runtime (önerilen)
# eclipse-temurin:21-jre-alpine ~150MB — Boyut kritikse

Java için multi-stage build çok yaygın: JDK ile build et, JRE ile çalıştır.

Go

# golang:1.22  ~800MB — Build için
# scratch      0B     — Runtime (Go static binary)

Go'da en küçük image'ları elde edersin çünkü Go, statik binary üretir — runtime'a ihtiyaç duymaz:

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /myapp .

FROM scratch
COPY --from=builder /myapp /myapp
CMD ["/myapp"]
# Final image: ~10MB

Tag Stratejileri — Versiyonlama Sanatı

Image tag'leri versiyon kontrolü sağlar. Doğru tag stratejisi, deployment süreçlerinin sağlığı için kritik.

Semantic Versioning (SemVer)

En yaygın ve önerilen strateji:

myapp:1.0.0    # MAJOR.MINOR.PATCH — en spesifik
myapp:1.0      # Minor — patch otomatik güncellenir
myapp:1        # Major — minor ve patch güncellenir
myapp:latest   # Tag belirtilmezse varsayılan

Her yeni release'de:

docker tag myapp:1.2.3 myapp:1.2
docker tag myapp:1.2.3 myapp:1
docker tag myapp:1.2.3 myapp:latest

Git-Based Tagging

VERSION=$(git rev-parse --short HEAD)
docker build -t myapp:${VERSION} .
# myapp:abc123f — hangi commit'ten build edildiği belli

latest Hakkında Son Bir Uyarı

latest ne "en yeni" ne "en güvenli" demek. Sadece "tag belirtilmediğinde varsayılan" demek.

Production Dockerfile'ında FROM node:latest yazma — FROM node:20.11.0 yaz. Production deploy'larında myapp:latest kullanma — myapp:v1.2.3 kullan. Geliştirmede kullanabilirsin ama production'da belirli versiyon şart.


Image Boyut Optimizasyonu

Küçük image neden önemli? Daha hızlı pull/push (CI/CD hızlanır). Daha az disk ve bant genişliği. Daha hızlı scaling (yeni instance'lar çabuk kalkar). Ve daha az saldırı yüzeyi (güvenlik).

Hızlı Optimizasyon Teknikleri

# 1. Doğru base image seç
FROM node:20-slim     # 220MB (1.1GB'lık full yerine)

# 2. RUN komutlarını birleştir
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

# 3. Cache temizle
RUN pip install --no-cache-dir -r requirements.txt
RUN npm ci && npm cache clean --force

# 4. .dockerignore kullan (node_modules, .git, test/ hariç tut)

Image Boyutunu Analiz Et

# Katman bazlı boyut
docker history myapp:v1

# dive ile detaylı analiz
docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive myapp:v1

Multi-Platform Image

Apple Silicon Mac'ler (M1/M2/M3) yaygınlaştıkça, hem AMD64 hem ARM64 destekleyen image'lar oluşturmak önemli hale geldi:

# Multi-platform builder oluştur
docker buildx create --name mybuilder --use

# Hem AMD64 hem ARM64 için build + push
docker buildx build \
    --platform linux/amd64,linux/arm64 \
    -t tolgahan/myapp:v1 \
    --push .

Kullanıcı docker pull yaptığında Docker otomatik olarak platformuna uygun olanı çeker. Tek tag, birden fazla platform.


Bu Derste Ne Öğrendik?

  • Base image seçimi projenin temelidir. Slim çoğu production senaryosu için en iyi tercih.

  • Full geliştirme, Slim production, Alpine boyut kritik, Distroless maximum güvenlik, Scratch statik binary.

  • Semantic versioning (v1.2.3) tag stratejisinin altın standardı. latest yerine belirli versiyon kullan.

  • Image boyutunu küçült: doğru base image + RUN birleştirme + cache temizleme + .dockerignore.

  • Multi-platform image oluştur (buildx ile) — ARM cihazlar için önemli.

  • Alpine'ı körü körüne tercih etme — musl libc uyumluluk sorunları yaşatabilir.


latest Tag'i — Derinlemesine

Bu konuyu bir kez daha vurgulayacağım çünkü Docker'ın en çok yanlış anlaşılan konsepti.

latest Ne Değil?

latest, "en yeni versiyon" demek değil. latest, sadece "tag belirtilmezse varsayılan" demek.

# Bu iki komut aynı şey
docker pull nginx
docker pull nginx:latest

# Bu iki komut da aynı şey
docker build -t myapp .
docker build -t myapp:latest .

latest Tuzakları

Birinci tuzak: latest otomatik güncellenmez.

docker build -t myapp:v1.0 .
docker build -t myapp:v2.0 .
# myapp:latest hâlâ v1.0'ı gösteriyor!
# Açıkça tag'lemezsen güncellenmez

İkinci tuzak: latest her pull'da farklı olabilir. Bugün nginx:latest 1.25.4'ü gösterebilir, yarın 1.27.0'ı. Araya breaking change girerse uygulamanız çöker.

Üçüncü tuzak: hangi versiyon çalışıyor belli olmaz.

docker ps
# IMAGE          STATUS
# myapp:latest   Up 5 days
# Bu hangi versiyon? v1.0? v2.0? v2.1? Belli değil!

latest Kuralları

Geliştirmede kullanılabilir — hızlı test için sorun yok. Ama production'da kullanma. Dockerfile'daki FROM satırında kullanma. Ve her zaman belirli bir versiyonla birlikte tag'le:

docker tag myapp:v1.2.3 myapp:latest
docker push myapp:v1.2.3
docker push myapp:latest

Image İmzalama — Güvenliği Bir Adım Öteye Taşı

Image'ının gerçekten senden geldiğini kanıtlamak istersen, imzalama kullanabilirsin.

Docker Content Trust (DCT)

# Content Trust'ı etkinleştir
export DOCKER_CONTENT_TRUST=1

# Push — otomatik imzalanır
docker push tolgahan/myapp:v1
# İlk seferde signing key'ler oluşturulur

# Pull — imza doğrulanır
docker pull tolgahan/myapp:v1
# İmza eşleşmezse pull reddedilir

Cosign (Modern Yaklaşım)

# Key pair oluştur
cosign generate-key-pair

# Image'ı imzala
cosign sign --key cosign.key tolgahan/myapp:v1

# İmzayı doğrula
cosign verify --key cosign.pub tolgahan/myapp:v1

İmzalama, supply chain security için önemli. "Bu image gerçekten bizim CI/CD'den mi geldi, yoksa biri araya girip değiştirdi mi?" sorusunun cevabını verir.


Gerçek Dünya Senaryosu: Production Image Pipeline

Büyük bir projede image pipeline'ı şöyle görünür:

#!/bin/bash
# production-build.sh
set -euo pipefail

APP="myapp"
VERSION=$(cat VERSION)
COMMIT=$(git rev-parse --short HEAD)
REGISTRY="registry.mycompany.com"

echo "=== Building ${APP}:${VERSION} ==="

# 1. Güvenlik taraması (base image)
echo "Scanning base image..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy image --severity CRITICAL node:20-slim

# 2. Build
echo "Building..."
docker build \
    --build-arg VERSION=${VERSION} \
    --build-arg COMMIT=${COMMIT} \
    -t ${REGISTRY}/${APP}:${VERSION} \
    -t ${REGISTRY}/${APP}:${VERSION}-${COMMIT} \
    -t ${REGISTRY}/${APP}:latest .

# 3. Final image güvenlik taraması
echo "Scanning final image..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy image --severity CRITICAL,HIGH \
    ${REGISTRY}/${APP}:${VERSION}

# 4. Push
echo "Pushing..."
docker push ${REGISTRY}/${APP}:${VERSION}
docker push ${REGISTRY}/${APP}:${VERSION}-${COMMIT}
docker push ${REGISTRY}/${APP}:latest

echo "=== Done: ${REGISTRY}/${APP}:${VERSION} ==="

Bu pipeline'da: build öncesi base image taranıyor, build yapılıyor, final image taranıyor, sorun yoksa push ediliyor. Her adım bir güvenlik katmanı.


Image Lifecycle Yönetimi

Image'lar sonsuza kadar tutulmaz. Eski versiyonları düzenli silmek gerekir:

# Lokal temizlik — 1 haftadan eski image'ları sil
docker image prune -a --filter "until=168h"

# AWS ECR'da lifecycle policy — son 10 image'ı tut
aws ecr put-lifecycle-policy --repository-name myapp --lifecycle-policy-text '{
  "rules": [{
    "rulePriority": 1,
    "description": "Keep last 10 images",
    "selection": {"tagStatus": "any", "countType": "imageCountMoreThan", "countNumber": 10},
    "action": {"type": "expire"}
  }]
}'

Rollback Stratejisi

Versiyon bazlı tag'lerin en büyük avantajı: kolay rollback.

# v1.2.3'te sorun çıktı → v1.2.2'ye dön
docker stop myapp-container
docker run -d --name myapp-container myapp:v1.2.2

latest kullanıyorsan rollback yapamazsın — hangi versiyon olduğu belli değil. İşte bu yüzden belirli versiyon kullanmak bu kadar önemli.


Yaygın Hatalar ve Çözümleri

Alpine'da Paket Bulunamıyor

docker run -it python:3.12-alpine bash
# exec: "bash": executable file not found

Alpine'da bash yok — sh kullan. Veya: RUN apk add --no-cache bash

Image Çok Büyük — Neden?

docker history myapp:v1 --format "{{.Size}}\t{{.CreatedBy}}" | sort -rh | head -5
# 500MB  COPY . /app     ← node_modules dahil mi?!

.dockerignore kontrol et — node_modules ve .git hariç tutulmalı.

Base Image Güncellemesi Breaking Change Getirdi

# ❌ FROM python:3   → 3.11'den 3.12'ye atlayabilir
# ✅ FROM python:3.12  → minor güncelleme alır, major almaz
# ✅✅ FROM python:3.12.1  → hiçbir otomatik güncelleme almaz

En güvenli: digest ile pin'le. Ama periyodik olarak güncelle — güvenlik yamaları önemli.


Bölüm 2 tamamlandı! Image dünyasını detaylıca öğrendin — image nedir, nasıl yönetilir, registry'ler, base image seçimi, tag stratejileri. Bir sonraki bölümde Dockerfile yazmayı öğreneceğiz — kendi image'larını sıfırdan oluşturacaksın.