Mert Özen Her Satırda Daha İleri
DevOps Docker — #04

Image'ın İçi: Layer'lar, Tag, Digest, Registry ve Multi-platform Manifest

Mert Özen 14 Kas 2026 13 dk 10 görüntülenme
Image'ın İçi: Layer'lar, Tag, Digest, Registry ve Multi-platform Manifest

Bir image; salt okunur layer'lardan, bir config'ten ve bunları listeleyen manifest'ten oluşur, multi-platform image'larda tag her mimari için ayrı manifest'e işaret eden bir index'i gösterir. Tag değişebilir, digest değişmez: lokal registry'de aynı tag'i başka image'a çevirip digest pin'in farkını görüyor, Docker Hub limit header'ını okuyor, containerd image store'un getirdiklerine ve latest yerine semver + SHA tag stratejisine bakıyoruz.

Sürüm notu: Bu yazıdaki örnekler Docker Engine 29.9.3 (containerd image store), Buildx 0.39, lokal registry için registry:3 (distribution 3.1.2) ve nginx 1.30 ile denenmiştir.

Önce Tahmin Et

docker pull nginx:1.30-alpine komutunu bugün ve üç ay sonra çalıştırıyorsun. İki seferde de aynı byte'ları mı alırsın? İkinci soru: aynı komutu x64 bir Windows makinesi ve arm64 bir sunucu çalıştırıyor. İkisi de aynı adı yazdı; ellerine aynı image mı geçti? Bu yazının sonunda iki soruya da "hayır, ama neden ve bunu nasıl kontrol edeceğini biliyorsun" diye cevap verebileceksin.

Layer'lar: Image Bir Dosya Değil, Bir Yığındır

Bir image tek bir arşiv dosyası değildir. Üst üste binen dosya sistemi farklarından (layer) ve onları nasıl çalıştıracağını söyleyen bir configuration'dan oluşur. Bookstore'un Angular uygulaması bookstore-web'i ileride nginx üzerinde yayımlayacağız; bu yüzden örnek olarak nginx:1.30-alpine'a bakalım. docker image history her layer'ı ve onu üreten Dockerfile satırını gösterir:

docker image history --platform linux/amd64 nginx:1.30-alpine
IMAGE          CREATED      CREATED BY                                      SIZE      COMMENT
901e593c95dc   8 days ago   RUN /bin/sh -c set -x     && apkArch="$(cat …   51.8MB    buildkit.dockerfile.v0
<missing>      8 days ago   ENV ACME_VERSION=0.4.1                          0B        buildkit.dockerfile.v0
<missing>      8 days ago   ENV NJS_RELEASE=1                               0B        buildkit.dockerfile.v0
<missing>      8 days ago   ENV NJS_VERSION=1.0.2                           0B        buildkit.dockerfile.v0
<missing>      8 days ago   CMD ["nginx" "-g" "daemon off;"]                0B        buildkit.dockerfile.v0
<missing>      8 days ago   STOPSIGNAL SIGQUIT                              0B        buildkit.dockerfile.v0
<missing>      8 days ago   EXPOSE map[80/tcp:{}]                           0B        buildkit.dockerfile.v0
<missing>      8 days ago   ENTRYPOINT ["/docker-entrypoint.sh"]            0B        buildkit.dockerfile.v0
<missing>      8 days ago   COPY 30-tune-worker-processes.sh /docker-ent…   16.4kB    buildkit.dockerfile.v0
<missing>      8 days ago   COPY 20-envsubst-on-templates.sh /docker-ent…   12.3kB    buildkit.dockerfile.v0
<missing>      8 days ago   COPY 15-local-resolvers.envsh /docker-entryp…   12.3kB    buildkit.dockerfile.v0
<missing>      8 days ago   COPY 10-listen-on-ipv6-by-default.sh /docker…   12.3kB    buildkit.dockerfile.v0
<missing>      8 days ago   COPY docker-entrypoint.sh / # buildkit          8.19kB    buildkit.dockerfile.v0
<missing>      8 days ago   RUN /bin/sh -c set -x     && addgroup -g 101…   5.62MB    buildkit.dockerfile.v0
<missing>      8 days ago   ENV DYNPKG_RELEASE=1                            0B        buildkit.dockerfile.v0
<missing>      8 days ago   ENV PKG_RELEASE=1                               0B        buildkit.dockerfile.v0
<missing>      8 days ago   ENV NGINX_VERSION=1.30.6                        0B        buildkit.dockerfile.v0
<missing>      8 days ago   LABEL maintainer=NGINX Docker Maintainers <d…   0B        buildkit.dockerfile.v0
<missing>      8 days ago   CMD ["/bin/sh"]                                 0B        buildkit.dockerfile.v0
<missing>      8 days ago   ADD alpine-minirootfs-3.24.4-x86_64.tar.gz /…   9.09MB    buildkit.dockerfile.v0

Aşağıdan yukarı okunur. En alttaki satır Alpine'ın kök dosya sistemini ekliyor (9,09 MB); bu, alpine:3 image'ının kendisi. Üstündeki RUN satırları dosya sistemi değiştirdiği için layer üretiyor; ENV, CMD, EXPOSE, STOPSIGNAL gibi satırların boyutu 0 B, çünkü yalnız configuration'a yazılıyorlar. <missing> bir hata değil: bu image başka bir makinede build edilip registry'den çekildi, ara adımların lokalde bir image ID'si yok. Dockerfile instruction'larının hangisinin layer, hangisinin metadata ürettiğini bir sonraki yazıda kendi Dockerfile'ımızla göreceğiz.

Configuration'ın kendisini docker image inspect verir:

docker image inspect --platform linux/amd64 nginx:1.30-alpine --format "Layers={{len .RootFS.Layers}} User={{.Config.User}} Entrypoint={{.Config.Entrypoint}} Cmd={{.Config.Cmd}} ExposedPorts={{.Config.ExposedPorts}} StopSignal={{.Config.StopSignal}}"
Layers=8 User= Entrypoint=[/docker-entrypoint.sh] Cmd=[nginx -g daemon off;] ExposedPorts=map[80/tcp:{}] StopSignal=SIGQUIT

Sekiz layer, boş User (yani container root olarak başlıyor; nginx worker'ları sonra 101 numaralı kullanıcıya düşüyor) ve bir önceki yazıda gördüğümüz SIGQUIT. Bir image'ı çalıştırmadan önce "hangi kullanıcıyla, hangi komutla, hangi port'u bekleyerek başlıyor?" sorusunun cevabı burada.

Copy-on-write: Container'ın Yazılabilir Layer'ı

Image'ın layer'ları salt okunurdur. Container başlarken Docker bu layer'ların üstüne boş, yazılabilir bir layer koyar ve hepsini tek bir dosya sistemi gibi gösterir. Container bir dosyayı değiştirmek istediğinde dosya önce yazılabilir layer'a kopyalanır, değişiklik orada yapılır (copy-on-write). Silinen dosya için de yazılabilir layer'a bir "silindi" işareti konur; alttaki layer'a dokunulmaz. docker diff bu farkı listeler:

docker run -d --name dockerseries-web nginx:1.30-alpine
docker exec dockerseries-web sh -c "echo \"<h1>Bookstore</h1>\" > /usr/share/nginx/html/index.html; rm /usr/share/nginx/html/50x.html"
docker diff dockerseries-web
docker ps -s --filter name=dockerseries-web --format "table {{.Names}}\t{{.Size}}"
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp
C /etc
C /etc/nginx
C /etc/nginx/conf.d
C /etc/nginx/conf.d/default.conf
C /run
A /run/nginx.pid
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
D /usr/share/nginx/html/50x.html
NAMES              SIZE
dockerseries-web   119kB (virtual 66.4MB)

A eklenen, C değişen, D silinen yol. Bizim iki değişikliğimizin (index.html ve 50x.html) yanında nginx'in kendi çalışma dosyaları ve entrypoint script'inin IPv6 için değiştirdiği default.conf da listede. Container'ın kendi boyutu 119 kB; "virtual" değer altındaki image'la birlikte toplam. Aynı image'dan on container açsan image layer'ları diskte bir kez durur, her container yalnız kendi yazılabilir layer'ını taşır. Container silinince bu layer da gider; kalıcı veri bu yüzden volume'lere yazılır (11. yazı).

Layer'lar image'lar arasında da paylaşılır. nginx'in en alttaki layer'ı Alpine'ın kendisi olduğu için nginx'i çektikten sonra alpine:3'ü çekince Docker o layer'ı tekrar indirmez:

docker pull --platform linux/amd64 alpine:3
3: Pulling from library/alpine
881fc932939e: Pulling fs layer
881fc932939e: Already exists
86ff264fe568: Download complete
c35a7d82b8fa: Download complete
Digest: sha256:f59abe656b692d901e184be3445e084786602108599d2a51d229c0f5a48c71bf
Status: Image is up to date for alpine:3
docker.io/library/alpine:3

Layer'ı tanımlayan şey adı değil içeriğinin hash'i (881fc932939e…). Aynı base image'ı kullanan servislerin hızlı çekilmesi ve diskte az yer kaplaması bundan.

Tag ve Digest

nginx:1.30-alpine'daki 1.30-alpine bir tag: registry'de bir isme bağlı, değiştirilebilir bir işaretçi. nginx ekibi 1.30.7'yi yayımladığında aynı tag yeni image'ı gösterecek; ilk sorunun cevabı bu yüzden "hayır". Değişmeyen kimlik digest'tir: içeriğin hash'i. Docker bunu SHA (Secure Hash Algorithm; içerikten sabit uzunlukta özet üreten algoritma ailesi) ailesinden SHA-256 ile hesaplar. Tag ile digest farkını kendi registry'mizde görelim. Docker Hub'a push etmek yerine resmi registry:3 image'ıyla lokal bir registry açtım:

docker run -d --name dockerseries-registry -p 5000:5000 registry:3
docker tag nginx:1.30-alpine localhost:5000/bookstore/web:latest
docker push --platform linux/amd64 localhost:5000/bookstore/web:latest
docker tag alpine:3 localhost:5000/bookstore/web:latest
docker push --platform linux/amd64 localhost:5000/bookstore/web:latest
latest: digest: sha256:bb8f722b4be005fcd7827816a136ab779fd40185d85b5171be915cc7fc0575c7 size: 2493
latest: digest: sha256:0f49f331d9212aff11b932863be4eb01cdf962969aa86dab7d1ee21ac6f5f52b size: 1022

Aynı latest tag'ine önce nginx'i, sonra (yanlışlıkla, diyelim) düz Alpine'ı push ettik. Registry hiçbir şey sormadı. Şimdi tag'le ve ilk digest'le çalıştıralım (lokal kopyayı silip registry'den çekerek; push'ta yalnız amd64'ü gönderdiğimiz için çalıştırırken de o platformu istiyorum):

docker rmi localhost:5000/bookstore/web:latest
docker run --rm --platform linux/amd64 localhost:5000/bookstore/web:latest sh -c "cat /etc/alpine-release; command -v nginx || echo no nginx"
docker run --rm --platform linux/amd64 localhost:5000/bookstore/web@sha256:bb8f722b4be005fcd7827816a136ab779fd40185d85b5171be915cc7fc0575c7 nginx -v
3.24.4
no nginx
nginx version: nginx/1.30.6

(İkinci komutun entrypoint log'larını çıkardım.) Tag artık nginx'siz bir image'ı gösteriyor; digest ise ilk push edilen nginx'i aynen getiriyor. image@sha256:… biçimi bu yüzden "tam olarak bunu çalıştır" demenin tek güvenilir yolu. Bir Dockerfile'ın FROM satırında ya da bir deployment manifest'inde digest pin'lemek, base image'ın sen fark etmeden değişmesini engeller; bedeli, güvenlik yamalarını da otomatik almaman. Bu yüzden digest pin'i genellikle Dependabot veya Renovate gibi, yeni digest çıktığında pull request açan bir araçla birlikte kullanırız (13. yazı).

Registry'ler ve Docker Hub Limitleri

Image adı aslında bir adrestir. nginx:1.30-alpine yazdığında Docker onu docker.io/library/nginx:1.30-alpine olarak tamamlar: registry docker.io, namespace library (resmi image'lar), repository nginx, tag 1.30-alpine. Bu seride göreceğin registry'ler:

  • Docker Hub (docker.io): varsayılan registry; resmi image'lar ve herkesin public repository'leri.
  • MCR (Microsoft Container Registry; Microsoft'un image'larını dağıttığı registry, mcr.microsoft.com): .NET, SQL Server ve diğer Microsoft image'ları.
  • GHCR (GitHub Container Registry; GitHub'ın paket servisinin image registry'si, ghcr.io): GitHub reposuyla aynı yetkilerle; GitHub Actions serisinin "Deploy 2" yazısında Bookstore'un image'ını oraya push ettik.
  • ACR (Azure Container Registry; Azure'un yönetilen registry servisi, <ad>.azurecr.io): Azure'da çalışan ortamlar için private registry.

Private bir registry'ye push etmeden önce docker login ghcr.io ile giriş yaparsın; Docker kimlik bilgisini işletim sisteminin credential store'una (Windows'ta Credential Manager) yazar. Kendi lokal registry'mizin içine HTTP API'siyle bakmak da öğretici:

curl -s localhost:5000/v2/_catalog
curl -s localhost:5000/v2/bookstore/web/tags/list
curl -s -D - -o /dev/null -H "Accept: application/vnd.oci.image.manifest.v1+json" -H "Accept: application/vnd.oci.image.index.v1+json" localhost:5000/v2/bookstore/web/manifests/latest | grep -i -E "content-type|docker-content-digest"
{"repositories":["bookstore/web"]}
{"name":"bookstore/web","tags":["latest"]}
Content-Type: application/vnd.oci.image.manifest.v1+json
Docker-Content-Digest: sha256:0f49f331d9212aff11b932863be4eb01cdf962969aa86dab7d1ee21ac6f5f52b

latest artık ikinci push'taki Alpine manifest'ini (0f49f331…) gösteriyor. docker pull'un yaptığı da bu: tag'i bir manifest'e çevirir, manifest'teki digest'leri tek tek indirir. Registry API'si OCI'nin (Open Container Initiative; container formatlarını standartlaştıran proje) distribution-spec'inde tanımlı; Docker Hub, GHCR, ACR ve registry:3 aynı API'yi konuşur.

Docker Hub ücretsiz kullanımda pull sayısını sınırlar. Docker'ın dokümanı Kasım 2026'da anonim kullanıcı için IPv4 adresi başına 6 saatte 100, ücretsiz Personal hesap için 6 saatte 200 pull yazıyor; çok platformlu bir image'da her mimari ayrı bir pull sayılıyor. Limiti dokümandan değil registry'nin kendi cevabından okumak daha güvenilir; Docker'ın önerdiği HEAD isteği limitten düşmez:

TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl -s --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i '^ratelimit'
ratelimit-limit: 100;w=3600
ratelimit-remaining: 45;w=3600

İlginç olan w=3600: bu header'ı aldığım gün pencere dokümandaki 6 saat (21600 saniye) değil 1 saatti. Limitler Docker'ın kararıyla değişebiliyor; CI'ın gerçekten neyle karşılaştığını bu header söyler. Aynı IP'yi paylaşan CI runner'ları anonim limiti hızla tüketir; çözüm runner'larda docker login yapmak, sık kullanılan base image'ları kendi registry'nde tutmak ya da Docker Hub için bir pull-through cache (mirror) kurmaktır.

Multi-platform Image: Index ve Manifest'ler

İkinci sorunun cevabı tag'in arkasında bir değil birden çok image olmasında. docker buildx imagetools inspect registry'deki yapıyı gösterir:

docker buildx imagetools inspect nginx:1.30-alpine
Name:      docker.io/library/nginx:1.30-alpine
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:901e593c95dc85f5a729f5765516321366ef4ac401821431d408897e95287d0a

Manifests:
  Name:        docker.io/library/nginx:1.30-alpine@sha256:bb8f722b4be005fcd7827816a136ab779fd40185d85b5171be915cc7fc0575c7
  MediaType:   application/vnd.oci.image.manifest.v1+json
  Platform:    linux/amd64
  ...
  Name:        docker.io/library/nginx:1.30-alpine@sha256:49c436e1cd6761d9bd9fefb92bd55b96edb6a05e8f810cbce1dfd6fe470f05d9
  MediaType:   application/vnd.oci.image.manifest.v1+json
  Platform:    unknown/unknown
  Annotations:
    vnd.docker.reference.type:                attestation-manifest
  ...

(Çıktıyı kısalttım: aynı listede linux/arm/v6, linux/arm/v7, linux/arm64/v8, linux/386, linux/ppc64le, linux/riscv64, linux/s390x ve her biri için bir attestation manifest'i daha var.) Tag bir image index'ini (Docker'ın eski adıyla manifest list) gösteriyor. Index, platform başına bir manifest'e işaret ediyor; her manifest de bir config'e ve layer listesine. unknown/unknown görünen kayıtlar çalıştırılabilir image değil, image'ın nasıl build edildiğini anlatan provenance ve SBOM (Software Bill of Materials; image'daki paketlerin ve sürümlerinin listesi) gibi attestation'lar; DevSecOps serisinde bunlarla çalışıyoruz. amd64 manifest'inin içi:

docker buildx imagetools inspect --raw nginx:1.30-alpine@sha256:bb8f722b4be005fcd7827816a136ab779fd40185d85b5171be915cc7fc0575c7
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:1390db6b01a8b0c865b157c7f036b0772534418b1701f598737de546d208b6e4",
    "size": 12296
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:881fc932939efef04ca120ad44bbd2c0917851eb247aa4995428afb1e80ede17",
      "size": 3853673
    },
    ...
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:4fd2a979a136065b9518cac19c8f3d11f2251813831dc2d18a4edb1d1b26374d",
      "size": 20321868
    }
  ],
  ...
}

Layer boyutları burada sıkıştırılmış (registry'de taşınan) boyutlar: Alpine layer'ı 3,9 MB iken docker image history açılmış hâlini 9,09 MB gösteriyordu. İlk layer'ın digest'i de az önce alpine:3'te "Already exists" diyen 881fc932939e. Aşağıdaki şekil bu dört katmanı bir arada gösteriyor.

04-image-anatomy-tr.png

x64 Windows makinen nginx:1.30-alpine'ı çektiğinde Docker index'ten linux/amd64'ü (bb8f722b…), arm64 sunucu linux/arm64/v8'i (564a0993…) seçer. İkisi aynı adı yazdı ama farklı layer'larla çalışıyor; ikinci sorunun cevabı da bu. Index'in digest'i ise içerikten hesaplandığı için registry'den registry'ye kopyalansa bile değişmez:

docker buildx imagetools create --tag localhost:5000/bookstore/web:1.0.0 nginx:1.30-alpine
docker buildx imagetools inspect localhost:5000/bookstore/web:1.0.0 | head -3
Name:      localhost:5000/bookstore/web:1.0.0
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:901e593c95dc85f5a729f5765516321366ef4ac401821431d408897e95287d0a

imagetools create bütün platformları ve attestation'ları registry'den registry'ye kopyaladı; lokal registry'deki index'in digest'i Docker Hub'dakiyle birebir aynı. Bir image'ı test ortamından production registry'sine taşırken "aynı şeyi mi taşıdım?" sorusunun cevabı bu eşitlik.

containerd Image Store

Docker uzun yıllar image'ları kendi graph driver'larıyla (en yaygını overlay2) sakladı ve bu yapı lokalde bir tag için yalnız bir platformu tutabiliyordu. Docker'ın dokümanına göre Engine 29.0'dan itibaren temiz kurulumlarda varsayılan, image'ları containerd'nin saklaması; yükseltilen kurulumlar overlay2'de kalıyor ve daemon.json'daki "features": {"containerd-snapshotter": true} ile geçiş yapabiliyor. Docker Desktop'ın yeni kurulumları da bu store'la geliyor. Hangisini kullandığını şöyle görürsün:

docker info -f "{{ .DriverStatus }}"
[[driver-type io.containerd.snapshotter.v1]]

Pratikte üç şey değişiyor. Birincisi, aynı tag'in birden çok platformu lokalde yan yana durabiliyor; --tree bunu gösterir:

docker image ls --tree nginx:1.30-alpine
IMAGE                   ID             DISK USAGE   CONTENT SIZE   EXTRA
nginx:1.30-alpine       901e593c95dc        187MB         53.8MB
├─ linux/amd64          bb8f722b4be0       92.7MB         26.1MB
├─ linux/arm/v6         dd71710d7e29           0B             0B
├─ linux/arm/v7         346f0b8473c5           0B             0B
├─ linux/arm64/v8       564a09935b00       92.2MB           26MB
├─ linux/386            7bec307c2255           0B             0B
├─ linux/ppc64le        cabc376da5e4           0B             0B
├─ linux/riscv64        d7f8c0e78a33           0B             0B
└─ linux/s390x          b774748ef661           0B             0B

Bu makinede iki platform indirilmiş, diğerleri yalnız index'te kayıtlı. İkincisi, image ID'si artık index'in digest'i (901e593c95dc), yani lokal ID ile registry'deki digest aynı şeyi söylüyor. Üçüncüsü disk: containerd hem sıkıştırılmış içeriği (CONTENT SIZE, 26,1 MB) hem açılmış layer'ları tutuyor; bir platformun diskte kapladığı yer (DISK USAGE, 92,7 MB) bu yüzden eski store'a göre daha fazla. Docker'ın dokümanı da bu yüzden docker system df ile disk kullanımını izlemeyi öneriyor. Bir yan etkisi de push'ta görülüyor: lokalde index'in bütün platformları yoksa Docker docker push'ta yalnız mevcut platformu gönderiyor ve "Not all multiplatform-content is present" diye bilgi veriyor; yukarıda --platform linux/amd64 vermemin nedeni bu.

Tag Stratejisi: latest Neden Yetmez?

latest özel bir anlam taşımaz; tag verilmezse kullanılan sıradan bir isimdir ve "en yeni" olduğunu kimse garanti etmez. Yukarıdaki deneyde latest'in bir push'la nginx'ten Alpine'a dönmesi tam da bu. Production'da hangi sürümün çalıştığını latest'ten çıkaramazsın, geri dönmek istediğinde de "bir önceki latest" diye bir şey yoktur.

Bookstore için önerdiğimiz düzen her build'e iki tag vermek: uygulamanın semver (anlamlı sürümleme; MAJOR.MINOR.PATCH) sürümü ve commit'in kısa SHA'sı, örneğin bookstore-api:1.4.2 ve bookstore-api:sha-3f9c2ab. Sürüm tag'i insanlar için, SHA tag'i "bu image hangi koddan çıktı?" sorusu için. Aynı sürüm numarasını ikinci kez push etmemek (immutable tag) bir ekip kuralı olmalı; Azure Container Registry ve Amazon Elastic Container Registry gibi registry'ler tag'leri değiştirilemez yapma seçeneği de sunuyor. Deploy ederken ise tag'i değil digest'i kaydet. Bu yaklaşımın pipeline tarafını CI/CD Temelleri serisinin "Build Once, Deploy Many: Artifact, Versiyonlama ve Immutable Paket" yazısında, GitHub Actions'taki uygulamasını GitHub Actions serisinin "Deploy 2" yazısında anlattık.

Üretimde Dikkat

Production'da çalışan her container için image digest'ini bil. docker inspect --format '{{.Image}}' ya da orkestratörün kaydı sana tag'i değil gerçekten çalışan içeriği söyler; bir güvenlik açığı duyurusunda "hangi servislerde bu base image var?" sorusunu digest'lerle cevaplarsın.

Base image'ları bir registry'nin erişilebilirliğine ve limitlerine bağlı bırakma. Docker Hub'ın limiti ya da kesintisi deploy'u durdurabilir. Kritik base image'ları kendi registry'ne kopyala (yukarıdaki imagetools create bunun en basit hâli) ve CI'da login ol.

Hedef sunucunun mimarisini bil ve image'ını o mimari için yayımla. Tek platformlu bir image'ı yanlış mimaride çalıştırmaya çalışmak ya hata verir ya da emülasyonla yavaş ve desteksiz çalışır. Kendi image'ımızı birden çok platform için build etmeyi 6. ve 9. yazıda göreceğiz.

Sık Düşülen Tuzaklar

FROM nginx:latest ya da hiç tag yazmamak. Bugün çalışan build yarın farklı bir base image'la çalışır; hata çıktığında neyin değiştiğini bulmak zorlaşır. En azından minor sürümü yaz, kritik image'larda digest pin'le.

Aynı tag'i yeniden push etmek. "1.4.2'yi düzelttim, aynı tag'le tekrar gönderdim" demek, o tag'i daha önce çekmiş sunucuların eski image'la kalması ve kimin hangisini çalıştırdığının bilinmemesi demek.

Image boyutuna yalnız docker image ls'teki tek sayıyla bakmak. Registry'de taşınan boyut sıkıştırılmış boyuttur, diskte kaplanan yer daha büyüktür ve paylaşılan layer'lar her image'da tekrar sayılır. Gerçek tabloyu docker image ls --tree ve docker system df verir.

Docker Hub limitini CI'da ilk kez "toomanyrequests" hatasıyla öğrenmek. Limit header'ını kontrol eden küçük bir adım ve login, sabah build'lerinin rastgele kırılmasını önler.

Küçük Bir Deneme

★☆☆ mcr.microsoft.com/dotnet/aspnet:10.0 için docker buildx imagetools inspect çalıştır. Hangi platformlar var? docker image history ile en büyük layer'ı bul; hangi instruction onu üretmiş?

★★☆ registry:3 ile lokal bir registry aç, bir image'ı iki farklı tag'le push et ve /v2/<repo>/tags/list ile tag'leri listele. Aynı image'ı iki tag'le push ettiğinde registry'ye kaç manifest ve kaç layer yazıldığını açıkla.

★★★ Bookstore'un base image'larını (aspnet:10.0, nginx:1.30-alpine) digest'le pin'leyen bir liste hazırla ve bu digest'leri haftalık kontrol edip değiştiğinde haber veren küçük bir script yaz (docker buildx imagetools inspect --format '{{json .Manifest}}' işini kolaylaştırır).

Image'ın içini gördük: layer'lar, config, manifest ve index. Bir sonraki yazıda bu yapıyı kendimiz üretiyoruz: Bookstore'un küçük bir yardımcı aracı için ilk Dockerfile'ı yazıp FROM, RUN, COPY, ENV, ARG, USER, CMD ve ENTRYPOINT'in her birinin image'a ne eklediğine bakıyoruz.