← Kursa Dön
📄 Text · 30 min

Image Nedir? Katmanlı Dosya Sistemi (Union FS)

Bir önceki bölümde container'ları oluşturup çalıştırdık. Her seferinde docker run nginx veya docker run postgres:16 gibi komutlar kullandık. Peki bu "nginx" veya "postgres:16" tam olarak ne? Nereden geliyor? İçinde ne var? İşte bu bölümde Docker'ın en temel kavramlarından birini — image kavramını — derinlemesine öğreneceğiz.

Bir pasta yapıyorsun diyelim. En altta pandispanya katmanı, üstüne krema, sonra meyve, en üste çikolata sosu. Her katman bir öncekinin üstüne ekleniyor. Ve her katman kendi başına bir anlam taşıyor. İşte Docker image'ları tam olarak böyle çalışıyor — katman katman üst üste yığılıyorlar.


Image'ın Basit Tanımı

Docker image, bir container'ı çalıştırmak için gereken her şeyin salt-okunur bir paketi. İçinde neler var?

İşletim sistemi dosyaları (temel kütüphaneler, shell, araçlar). Uygulamanın runtime'ı (Node.js, Python, Java gibi). Uygulama kodu. Bağımlılıklar (node_modules, pip packages gibi). Konfigürasyon dosyaları. Ve metadata (hangi portu kullanıyor, hangi komutla başlıyor gibi bilgiler).

Şunu net anla: image çalıştırılmaz. Image'dan container oluşturulur ve container çalıştırılır. Image şablon, container o şablondan yapılmış çalışan kopya. Bunu Bölüm 1'de öğrenmiştik, ama şimdi image'ın iç yapısını daha detaylı keşfedeceğiz.

# Image'ları listele
docker images
# REPOSITORY   TAG       IMAGE ID       SIZE
# nginx        1.25      a8758716bb6a   187MB
# node         20        5c2e4c9a24b3   1.1GB
# alpine       3.19      c1aabb73d233   7.38MB

Boyut farklarına dikkat et. Alpine 7MB, Node.js 1.1GB. Neden bu kadar fark var? Cevap, image'ların katmanlı yapısında gizli.


Katmanlı Dosya Sistemi — Docker'ın En Akıllı Tasarım Kararı

Docker image'ları düz bir dosya değil — katmanlardan (layers) oluşuyor. Her katman, bir önceki katmanın üzerine eklenen bir değişikliği temsil ediyor. Bu, Docker'ın en akıllı tasarım kararlarından biri çünkü muazzam bir verimlilik sağlıyor.

Bunu şöyle düşün. Bir Dockerfile'da (image'ın tarifi — Bölüm 3'te detaylıca öğreneceğiz) her komut bir katman oluşturuyor:

┌─────────────────────────────────────┐
│  Container Layer (yazılabilir)       │ ← Sadece container'da
├─────────────────────────────────────┤
│  Layer 5: COPY app.js /app/         │ ← Uygulama kodu
├─────────────────────────────────────┤
│  Layer 4: RUN npm install           │ ← Bağımlılıklar
├─────────────────────────────────────┤
│  Layer 3: COPY package.json /app/   │ ← Paket dosyası
├─────────────────────────────────────┤
│  Layer 2: RUN apt-get install curl  │ ← Ek paketler
├─────────────────────────────────────┤
│  Layer 1: Ubuntu 22.04 base         │ ← Temel işletim sistemi
└─────────────────────────────────────┘

Her katman sadece bir önceki katmana göre değişiklikleri (diff) içeriyor. Ubuntu base katmanı 77MB diyelim. Üstüne curl yüklediğinde sadece curl'ün dosyaları ekleniyor (birkaç MB). package.json kopyalandığında sadece o dosya ekleniyor. Ve böyle devam ediyor.

Katmanlar Neden Paylaşılır?

İşte asıl büyülü kısım burada. Diyelim ki iki farklı Node.js uygulamanız var:

# App A
FROM node:20-alpine
COPY package.json /app/
RUN npm install
COPY . /app/

# App B
FROM node:20-alpine
COPY package.json /app/
RUN npm install
COPY . /app/

İkisi de node:20-alpine base image'ını kullanıyor. Docker bu durumda node:20-alpine katmanını disk üzerinde sadece bir kez saklıyor. İki image de aynı katmana referans veriyor.

docker images
# REPOSITORY   TAG     SIZE
# app-a        latest  180MB
# app-b        latest  175MB
# Toplam gösterilen: 355MB
# Gerçek disk kullanımı: ~200MB (base image paylaşımlı!)

Gerçek disk kullanımını görmek istersen:

docker system df
# TYPE     TOTAL   ACTIVE  SIZE     RECLAIMABLE
# Images   3       2       200MB    50MB (25%)

Bu paylaşım mekanizması, Docker'ın disk ve bant genişliği açısından çok verimli olmasının ana sebebi.

Union File System — Katmanları Birleştirme

Peki container çalışırken bu katmanlar nasıl birleşiyor? Docker, overlay2 (OverlayFS) adlı bir dosya sistemi sürücüsü kullanıyor. Bu sürücü, salt-okunur image katmanlarını ve container'ın yazılabilir katmanını tek bir dosya sistemi olarak birleştiriyor.

Container bir dosyayı okumak istediğinde, en üst katmandan başlayarak aşağıya doğru arar. Dosyayı bulduğu ilk katmandan okur.

Container bir dosyayı değiştirmek istediğinde ise Copy-on-Write (CoW) mekanizması devreye girer. Dosya önce image katmanından container'ın yazılabilir katmanına kopyalanır, sonra değişiklik kopya üzerinde yapılır. Orijinal image katmanı hiç değişmez.

# Nginx container'ı başlatalım
docker run -d --name web nginx

# Container içinde bir dosya değiştirelim
docker exec web sh -c 'echo "Merhaba" > /usr/share/nginx/html/index.html'

# Bu değişiklik sadece BU container'ın yazılabilir katmanında
# Image değişmedi! Başka container'lar etkilenmez

# Container silinirse değişiklik de gider
docker rm -f web

Bu yüzden container'ları "geçici" (ephemeral) olarak düşünüyoruz. Container'ın yazılabilir katmanında saklanan veriler, container silindiğinde kaybolur. Kalıcı veri istiyorsan volume kullanmalısın.


Image Katmanlarını İnceleme

Image'ların katmanlarını görmek ve analiz etmek için birkaç araç var.

docker history — Build Geçmişi

docker history nginx:1.25-alpine

Bu komut image'ın her katmanını, ne zaman oluşturulduğunu, hangi komutla oluşturulduğunu ve boyutunu gösterir. Hangi katmanın en çok yer kapladığını görmek için harika bir araç.

En büyük katmanları bulmak istersen:

docker history nginx:1.25 --format "{{.Size}}\t{{.CreatedBy}}" | sort -rh | head -5

docker inspect — Tüm Metadata

docker inspect nginx:1.25-alpine --format '{{.Config.Cmd}}'
# Varsayılan komut: [nginx -g daemon off;]

docker inspect nginx:1.25-alpine --format '{{.Config.ExposedPorts}}'
# Açık portlar: map[80/tcp:{}]

docker inspect nginx:1.25-alpine --format '{{len .RootFS.Layers}}'
# Katman sayısı: 7

docker inspect image hakkında bilmek isteyebileceğin her şeyi JSON formatında verir. İleriki derslerde bu komutu çok kullanacağız.

dive — Görsel Katman Analizi

docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive nginx:1.25-alpine

dive, interaktif bir araçtır — her katmanda hangi dosyaların eklendiğini, değiştiğini, silindiğini gösterir. Image optimizasyonu yaparken vazgeçilmez.


Image Tag'leri ve Versiyonlama

Bir image'ın birden fazla versiyonu olabilir. Bu versiyonları tag'ler ile ayırt ederiz.

docker pull node:20           # Node.js 20, varsayılan (full Debian)
docker pull node:20.11.0      # Tam versiyon numarası
docker pull node:20-alpine    # Alpine Linux tabanlı (küçük)
docker pull node:20-slim      # Slim Debian tabanlı (orta)
docker pull node:lts           # Long Term Support versiyonu

Burada bazıları aynı image'ı gösteriyor olabilir:

docker images node
# REPOSITORY   TAG       IMAGE ID       SIZE
# node         20        5c2e4c9a24b3   1.1GB
# node         20.11     5c2e4c9a24b3   1.1GB  ← Aynı IMAGE ID!
# node         20.11.0   5c2e4c9a24b3   1.1GB  ← Aynı IMAGE ID!

node:20, node:20.11 ve node:20.11.0 aynı image'a üç farklı isim takmış gibi. IMAGE ID aynıysa, disk üzerinde tek bir kopya var.

⚠️ latest Tag Tuzağı

Docker dünyasının en büyük tuzaklarından biri latest tag'i. Adı "en son" diyor ama yanıltıcı.

docker pull nginx         # = nginx:latest

latest aslında sadece "tag belirtilmezse varsayılan" demek. "En yeni versiyon" garanti etmez! Bugün nginx:latest 1.25.4'ü gösterebilir, yarın 1.27.0'ı. Bu da breaking change riski demek.

Production'da her zaman belirli versiyon kullan:

# ❌ Riskli
docker pull nginx:latest

# ✅ Güvenli
docker pull nginx:1.25.4

Image Varyantları — Alpine, Slim, Full

Aynı yazılım farklı "tadlarda" sunuluyor. Her birinin farklı bir kullanım senaryosu var.

Full (Default) — Her Şey Dahil

docker pull python:3.12      # ~1GB

İçinde Debian, build tools (gcc, make), ve her türlü kütüphane var. Geliştirme ve C extension build etmen gereken durumlar için ideal.

Slim — Sadece Gerekli Olan

docker pull python:3.12-slim  # ~150MB

Debian'ın minimal versiyonu. Build tools yok ama Python çalışması için gereken her şey var. Production için çoğu zaman en iyi tercih.

Alpine — Minimum Boyut

docker pull python:3.12-alpine  # ~51MB

Alpine Linux tabanlı — çok küçük, çok hafif. Ama bir uyarı: Alpine, standart glibc yerine musl libc kullanıyor. Bu da bazı C kütüphaneleriyle uyumluluk sorunlarına yol açabilir.

# 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 ek bağımlılık gerektirebilir. Slim kullanmak genellikle daha az sorunlu.

Boyut Karşılaştırması

docker images --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}" | sort
# python:3.12              1.02GB
# python:3.12-slim         150MB
# python:3.12-alpine       51MB
# node:20                  1.1GB
# node:20-slim             220MB
# node:20-alpine           130MB

Genel kuralım şu: geliştirmede full veya slim, production'da slim, boyut kritik ise alpine. Alpine'ı körü körüne tercih etme — uyumluluk sorunları yaşayabilirsin.


Image Oluşturma — İki Yol

Image'ları iki şekilde oluşturabilirsin:

1. Dockerfile ile (Standart Yöntem)

FROM node:20-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD ["node", "server.js"]
docker build -t myapp:v1.0 .

Bu yöntemi Bölüm 3'te detaylıca öğreneceğiz. Kısa, tekrarlanabilir, otomatize edilebilir.

2. Container'dan Commit ile (Önerilmez)

docker run -it ubuntu bash
# İçeride: apt update && apt install -y curl vim
# exit

docker commit <container_id> myimage:v1

Bu yöntem neden önerilmez? Tekrarlanabilir değil — ne yüklediğini hatırlaman lazım. Otomatize edilemez. Katmanlar optimize değil. Güvenlik taraması yapılamaz. Her zaman Dockerfile kullan.


Image Digest — Değişmez Referans

Tag'ler değişebilir ama digest asla değişmez. Digest, image'ın SHA256 hash'i.

docker inspect nginx:1.25 --format '{{index .RepoDigests 0}}'
# nginx@sha256:6db391d1c0cfb30588ba0bf72ea999404f2764f...

# Digest ile pull — %100 aynı image'ı garanti eder
docker pull nginx@sha256:6db391d1c0cfb30588ba0bf72ea999404f2764f...

Production'da en güvenli yöntem digest kullanmak. Ama pratikte semantic versioning (nginx:1.25.4) çoğu senaryo için yeterli.


Dangling Images — Havada Kalan Image'lar

Build sırasında eski katmanlar "dangling" (tag'siz, havada kalan) olabilir:

docker images -f "dangling=true"
# REPOSITORY   TAG     IMAGE ID       SIZE
# <none>       <none>  abc123         150MB

Bunlar disk israfı. Temizle:

docker image prune -f

Image Yönetim Komutları — Hızlı Referans

# Listeleme
docker images                    # Tüm image'lar
docker images node               # Sadece node image'ları

# İndirme
docker pull nginx:1.25           # Belirli versiyon
docker pull --platform linux/arm64 nginx  # Belirli platform

# Tag'leme
docker tag nginx:1.25 my-nginx:latest

# Silme
docker rmi nginx:1.25            # Image sil
docker image prune               # Dangling image'ları sil
docker image prune -a            # Kullanılmayan tüm image'ları sil

# Kaydetme / Yükleme (offline transfer)
docker save nginx:1.25 | gzip > nginx.tar.gz    # Dosyaya kaydet
gunzip -c nginx.tar.gz | docker load            # Dosyadan yükle

save/load özellikle internet olmayan ortamlara image taşımak için çok kullanışlı. USB'ye kopyala, götür, yükle.


Image'ın İç Yapısını Anlamak — Pratik Örnekler

Teoriden pratiğe geçelim. Image katmanlarını gerçekten görelim ve nasıl çalıştığını elle deneyelim.

Katman Paylaşımını Gözlemle

# İlk image'ı indir — tüm katmanlar indirilecek
docker pull node:20-alpine
# a2abf6c4d29d: Pull complete  ← Alpine base
# f19e5a72315b: Pull complete  ← Node.js binary
# 7c2d5c8e3a05: Pull complete  ← npm/yarn

# İkinci image'ı indir — paylaşılan katmanlar atlanacak!
docker pull node:20-slim
# a2abf6c4d29d: Already exists  ← Bu katman zaten var, atlanıyor!
# b8c3a12e5f67: Pull complete   ← Sadece farklı katmanlar indiriliyor

"Already exists" mesajlarını gördün mü? Docker, zaten sahip olduğu katmanları tekrar indirmiyor. Bu, hem bant genişliğinden hem diskten tasarruf sağlıyor. 10 farklı Node.js uygulamasının image'ını indirsen bile, node:20-alpine base katmanı sadece bir kez disk'te yer kaplıyor.

Copy-on-Write'ı Gözlemle

# Nginx'ten bir container başlat
docker run -d --name test-cow nginx

# Orijinal dosyayı oku
docker exec test-cow cat /usr/share/nginx/html/index.html
# Nginx'in varsayılan HTML sayfası görünür

# Dosyayı değiştir
docker exec test-cow sh -c 'echo "Değiştirildi!" > /usr/share/nginx/html/index.html'

# Container'daki değişikliği gör
docker exec test-cow cat /usr/share/nginx/html/index.html
# Değiştirildi!

# Image'dan yeni bir container daha oluştur
docker run -d --name test-cow-2 nginx
docker exec test-cow-2 cat /usr/share/nginx/html/index.html
# Orijinal sayfa! — image değişmedi, değişiklik sadece ilk container'da

# docker diff ile değişiklikleri gör
docker diff test-cow
# C /usr/share/nginx/html/index.html  ← Changed!

# Temizlik
docker rm -f test-cow test-cow-2

Bu deney çok önemli bir şeyi gösteriyor: container'da yapılan değişiklikler sadece o container'a özel. Image hiç değişmiyor. Başka container'lar etkilenmiyor. Ve container silindiğinde değişiklikler de kayboluyor.

Image Boyutunu Gerçekten Anlamak

Docker'da boyutlar ilk bakışta kafa karıştırıcı olabilir. docker images komutu her image'ın boyutunu gösterir ama bu boyutlar paylaşılan katmanları içerir. Gerçek disk kullanımını görmek için:

docker system df -v

Bu komut her image'ın gerçek boyutunu, paylaşılan boyutu ve geri kazanılabilir alanı gösterir.

Bir de şu ilginç duruma bakalım:

# Aynı image'a farklı tag'ler verdiğimizde
docker tag nginx:1.25 my-nginx:v1
docker tag nginx:1.25 my-nginx:v2
docker tag nginx:1.25 my-nginx:latest

docker images | grep -E "nginx|my-nginx"
# nginx      1.25      a8758716bb6a   187MB
# my-nginx   v1        a8758716bb6a   187MB
# my-nginx   v2        a8758716bb6a   187MB
# my-nginx   latest    a8758716bb6a   187MB

Dört farklı isim ama hepsi aynı IMAGE ID'ye sahip! Disk'te tek bir kopya var. docker tag bir kopyalama yapmaz, sadece aynı image'a yeni bir referans (isim) ekler.


Yaygın Hatalar ve Çözümleri

"No space left on device" — Disk Dolmuş

Docker zamanla çok yer kaplar. Özellikle sık build yapıyorsan:

# Ne kadar yer kapladığını gör
docker system df
# TYPE          TOTAL   ACTIVE  SIZE      RECLAIMABLE
# Images        25      5       8.5GB     6.2GB (72%)
# Build Cache   -       -       3.1GB     3.1GB
# Containers    15      3       500MB     400MB

# Büyük temizlik
docker system prune -a --volumes
# ⚠️ Kullanılmayan her şeyi siler — dikkatli ol

Image Pull Çok Yavaş

Registry mirror kullanarak pull hızını artırabilirsin:

// /etc/docker/daemon.json
{
  "registry-mirrors": ["https://mirror.gcr.io"]
}
sudo systemctl restart docker

Alpine'da C Extension Build Hatası

docker run -it python:3.12-alpine pip install pandas
# ERROR: Could not build wheels for numpy

İki çözüm var: Ya gerekli build bağımlılıklarını kur (apk add gcc musl-dev) ya da slim image kullan. Slim genellikle daha az sorunlu:

docker run -it python:3.12-slim pip install pandas
# 30 saniye, sorunsuz ✓

Bu Derste Ne Öğrendik?

  • Docker image, uygulamanın çalışması için gereken her şeyin salt-okunur, katmanlı bir paketi.

  • Katmanlar (layers) paylaşılır — aynı base image'ı kullanan 10 uygulama, disk'te base image'ı sadece 1 kez tutar. Bu muazzam bir verimlilik.

  • Union File System (overlay2) katmanları birleştirir. Container yazarken Copy-on-Write kullanılır — image hiç değişmez.

  • Image tag'leri versiyonlama sağlar. `latest` yerine belirli versiyon kullanlatest yanıltıcı bir isim.

  • Full (~1GB) geliştirme için, Slim (~150MB) production için, Alpine (~50MB) boyut kritik ise. Ama Alpine'ın uyumluluk risklerini bil.

  • docker history, docker inspect ve dive ile image'ların içini inceleyebilirsin.

Bir sonraki derste image yönetiminin pratik yönlerini öğreneceğiz — pull, push, tag, inspect, temizleme stratejileri.