Açık Kaynak Uygulamalar

    Docker Image İnmiyor: toomanyrequests Pull Limiti Hatası ve Çözümü

    Docker Hub pull limitinin neden kişisel değil IP bazlı olduğunu ve kimlikli çekim, mirror ve alternatif registry ile kalıcı çözümü anlatır.

    12 dk okuma Güncellendi: 18 Ağustos 2026

    Akşam altıda çıkacak sürüm için sunucuda docker compose pull çalıştırıyorsunuz. İlk üç servis iniyor, dördüncüsünde satır duruyor:

    Error response from daemon: toomanyrequests: You have reached your pull rate limit.
    You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit
    

    İlk refleks genelde şudur: "Ben bugün beş imaj çektim, olacak iş değil." Haklısınız da; sorun sizin çektiğiniz imaj sayısı değil. Docker Hub anonim çekimleri hesaba değil çıkış IP adresine yazar. Yani aynı NAT'ın arkasındaki diğer sunucular, aynı ofisten çıkan geliştirici makineleri ve her derlemede yeniden imaj indiren CI hattınız sizinle aynı kotayı paylaşır. Kotayı bitiren komşunuz olabilir ve siz hiçbir şey yapmadan kapıda kalabilirsiniz.

    Bu yazıda önce hatanın mekanizmasını doğru kuracağız: limit kimin üzerine yazılıyor, tam olarak ne "pull" sayılıyor ve kalan hakkınızı nasıl ölçüyorsunuz. Ardından kalıcılığı artan sırayla dört çözüm göreceğiz — kimlikli çekim, kritik imajları kendi registry'nize kopyalama, pull-through cache kurma ve alternatif registry'lere geçme. Sonunda da üretimde uyulması gereken tek cümlelik kuralı koyacağız. Docker kavramlarına yeniyseniz önce Docker nedir başlangıç rehberi yazısına göz atmanız burayı çok daha okunur kılar.

    Hata Ne Diyor, Nereden Geliyor?#

    toomanyrequests, Docker daemon'ın uydurduğu bir mesaj değildir. registry-1.docker.io adresi isteğinize HTTP 429 yanıtı döndürür, daemon da bunu bu metne çevirip size gösterir. Yani bu bir ağ arızası, disk sorunu ya da imajın bozuk olması değildir; karşı taraf sizi bilerek geri çevirmiştir. HTTP 429'un genel mantığını 429 Too Many Requests hatası yazısında ayrıca ele almıştık; buradaki fark, limiti koyan tarafın sizin sunucunuz değil Docker Hub olmasıdır.

    Benzer görünen ama tamamen başka sebepleri olan hataları ayırt edin; yanlış teşhis saatler yakar:

    Hata metniGerçek sebepNe yapmalı
    toomanyrequests: You have reached your pull rate limitKota dolduBu yazının tamamı
    unauthorized: incorrect username or passworddocker login başarısızParola yerine erişim jetonu kullanın
    pull access denied ... repository does not existÖzel depo veya yanlış adDepo adını ve yetkiyi kontrol edin
    manifest unknownEtiket yokEtiketi ve mimariyi doğrulayın
    net/http: TLS handshake timeoutAğ veya proxyKota ile ilgisi yok

    Limit Kimin Üzerine Yazılıyor: IP mi, Hesap mı?#

    Bütün mesele bu ayrımdadır. Docker Hub belgelerine göre çekim hakkı iki farklı şekilde muhasebeleştirilir:

    DurumKota nereye yazılırBelgelenen sınır
    Kimliksiz (anonim)IPv4 adresi veya IPv6 /64 alt ağı6 saatte 100 çekim
    Docker Personal (ücretsiz, kimlikli)Hesap6 saatte 200 çekim
    Pro / Team / BusinessHesap veya kuruluşSınırsız

    Bu rakamları ezberlemeyin. Docker son yıllarda limitleri birkaç kez değiştirdi, bir kısmını duyurup sonra erteledi; Türkçe kaynakların büyük bölümü hâlâ eskimiş sayılar yazıyor. Doğru refleks, sayıya inanmak yerine bir sonraki bölümdeki komutla kendi sunucunuzdaki güncel değeri ölçmektir.

    Asıl anlaşılması gereken, ilk satırdaki "IPv4 adresi veya IPv6 /64 alt ağı" ifadesidir. Pratikteki karşılıkları şunlardır:

    • Paylaşımlı çıkış IP'si. Aynı ağ geçidinin arkasındaki bütün sunucular tek bir kotayı bölüşür. Bulut sağlayıcıların NAT ağ geçitleri, ofis internetleri ve bazı barındırma altyapıları bu tabloya girer.
    • Kubernetes düğümleri. Kümedeki her düğüm kendi imajını çeker. On düğümlü bir kümede tek bir DaemonSet güncellemesi on çekim demektir; hepsi aynı NAT'tan çıkıyorsa kotanın tamamı bir anda gider.
    • CI hattı. Her derlemede temiz bir çalıştırıcı ayağa kalkar, önbellek yoktur, temel imaj sıfırdan iner. Günde kırk derleme yapan bir ekip anonim kotayı öğleden önce bitirir.
    • IPv6 kullanıyorsanız kota tek adrese değil, /64 alt ağınızın tamamına yazılır — yani aynı sunucudaki farklı IPv6 adresleri size ek hak kazandırmaz.

    Buradan çıkan sonuç net: "az çektim" savunması geçersizdir, çünkü sayaç sizin değildir. Kotayı kişiselleştirmenin tek yolu kimlikli çekime geçmektir.

    Kalan Hakkınızı Ölçün: ratelimit-remaining#

    Tahmin yürütmek yerine sunucunun kendisine sorun. Docker Hub, kalan hakkı yanıt başlıklarında yayınlar. Aşağıdaki komut bir jeton alır ve manifest'e HEAD isteği atar; HEAD istekleri kotadan düşmez, GET ise gerçek bir çekim sayılır — bu yüzden --head parametresi tesadüf değildir:

    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'
    

    Tipik çıktı şuna benzer:

    ratelimit-limit: 100;w=21600
    ratelimit-remaining: 12;w=21600
    docker-ratelimit-source: 185.12.44.9
    

    Üç satırın da ayrı bir anlamı var. w=21600 pencerenin saniye cinsinden uzunluğudur, yani 6 saat. ratelimit-remaining o pencerede kalan hakkınızdır. En kritik olanı üçüncüsüdür: docker-ratelimit-source size kotanın kimin üzerine yazıldığını söyler. Orada bir IP adresi görüyorsanız anonim çekim yapıyorsunuzdur; kullanıcı adınızı görüyorsanız kimlikli çekim gerçekten devrededir.

    Aynı ölçümü kimlikli olarak yapmak için jetonu kullanıcı adı ve erişim jetonuyla alın:

    TOKEN=$(curl -s -u "kullaniciadiniz:$DOCKERHUB_PAT" \
      "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'
    

    İki çıktıyı yan yana koyduğunuzda docker login komutunuzun gerçekten işe yarayıp yaramadığını kanıtlamış olursunuz. Bu, aşağıdaki en yaygın tuzağın da tek doğrulama yöntemidir.

    Bir Pull Tam Olarak Nedir? Ne Sayılır, Ne Sayılmaz?#

    Sayacın neden beklediğinizden hızlı ilerlediğini anlamak için "çekim" tanımını netleştirmek gerekir. Docker'ın tanımına göre bir çekim, sürüm kontrolünü ve bunun sonucunda gerçekleşen indirmeyi birlikte kapsar. Bunun üç önemli sonucu var:

    1. İmaj yerelde duruyor olsa bile sayaç işleyebilir. docker pull nginx:1.27-alpine komutu, katmanlar zaten diskteyken bile etiketin hâlâ aynı manifest'i gösterip göstermediğini sorar. Hiçbir bayt inmez ama istek yapılmıştır.
    2. Çok mimarili imajlar mimari başına sayılır. Aynı etiketin amd64 ve arm64 sürümlerini çekiyorsanız bu iki çekimdir.
    3. docker compose pull servis sayısı kadar çekimdir. Sekiz servisli bir yığında hiçbir şey değişmemiş olsa bile tek komut sekiz istek üretir. Yığını günde birkaç kez yeniden başlatan bir ekip, kotayı fark etmeden tüketir.

    Buna bir de :latest alışkanlığı eklenir. Sabit sürüm etiketi yerine latest kullanan bir yapılandırmada hemen her araç "acaba değişti mi" diye sormak zorundadır; sabit etiket veya digest kullanmak hem bu isteklerin bir kısmını gereksiz kılar hem de dağıtımınızı öngörülebilir yapar.

    Çözüm 1: docker login ile Kimlikli Çekim#

    En hızlı ve çoğu durumda yeterli çözüm budur. Ücretsiz bir Docker hesabı bile kotayı IP'den alıp size taşır; artık komşunuzun ne yaptığı sizi ilgilendirmez.

    Sunucuda hesap parolanızı kullanmayın, Docker Hub arayüzünden bir kişisel erişim jetonu (PAT) üretin ve yalnızca okuma yetkisi verin. Jeton çalınırsa tek tek iptal edilir, parolanız etkilenmez:

    # Jetonu komut geçmişine düşürmeden gönderin
    echo "$DOCKERHUB_PAT" | docker login -u kullaniciadiniz --password-stdin
    

    Kimlik bilgisi ~/.docker/config.json dosyasına yazılır. Burada bilinmesi gereken bir ayrıntı var: bu dosyadaki değer şifreli değildir, yalnızca base64 ile kodlanmıştır ve tek komutla geri okunur:

    cat ~/.docker/config.json
    chmod 600 ~/.docker/config.json
    

    Bu yüzden dosyanın izinlerini daraltın ve mümkünse bir kimlik yardımcısı (docker-credential-pass, docker-credential-secretservice) kullanın.

    Şimdi asıl tuzak. docker login komutu, onu çalıştıran kullanıcının ev dizinine yazar. Sunucularda çok yaygın olan şu dizi sessizce anonim çekim yapar:

    docker login -u kullaniciadiniz     # ~/.docker/config.json yazıldı
    sudo docker pull nginx:1.27-alpine  # ama bu /root/.docker/config.json okur
    

    sudo varsayılan olarak HOME değişkenini root'un ev dizinine çevirir, dolayısıyla az önce oluşturduğunuz kimlik bilgisi hiç okunmaz. docker-ratelimit-source başlığında hâlâ bir IP adresi görüyorsanız neredeyse kesinlikle sebebi budur. Üç çözümü vardır: kullanıcıyı docker grubuna ekleyip sudo kullanmamak, root olarak bir kez giriş yapmak, ya da yapılandırma dizinini açıkça göstermek:

    sudo DOCKER_CONFIG=$HOME/.docker docker pull nginx:1.27-alpine
    

    Aynı mantık systemd birimleri için de geçerlidir. Konteynerleri bir birim dosyasıyla ayağa kaldırıyorsanız birim root olarak çalışır ve root'un yapılandırmasını okur:

    [Service]
    Environment=DOCKER_CONFIG=/etc/docker-cli
    ExecStart=/usr/bin/docker compose -f /srv/app/compose.yml up -d
    

    Kubernetes tarafında ise docker login hiçbir işe yaramaz; kimlik bilgisi bir imagePullSecrets nesnesi olarak tanımlanıp ServiceAccount'a bağlanmalıdır, çünkü imajı çeken şey kubelet'in konteyner çalışma zamanıdır.

    Çözüm 2: Kritik İmajları Kendi Registry'nize Kopyalayın#

    Kimlikli çekim kotayı büyütür ama bağımlılığı ortadan kaldırmaz. Üretim yığınınızın ayağa kalkması hâlâ dış bir servisin erişilebilir ve cömert olmasına bağlıdır. Bir sonraki adım, bağımlı olduğunuz temel imajları kendi registry'nize kopyalamaktır.

    Bunun için imajı indirip yeniden etiketlemek zorunda değilsiniz; skopeo ve crane bu işi Docker daemon'a hiç dokunmadan, kayıt defterinden kayıt defterine yapar:

    # Tek komutta registry'den registry'ye kopyalama
    skopeo copy docker://docker.io/library/nginx:1.27-alpine \
                docker://registry.ornek.com/base/nginx:1.27-alpine
    
    # crane ile aynı iş
    crane copy docker.io/library/redis:7.4 registry.ornek.com/base/redis:7.4
    

    Kopyaladıktan sonra Dockerfile ve compose dosyalarınızdaki referansları kendi adresinize çevirin. Etiketi digest ile sabitlerseniz, aynı içeriği çektiğinizden emin olursunuz:

    docker inspect --format='{{index .RepoDigests 0}}' nginx:1.27-alpine
    
    FROM registry.ornek.com/base/nginx@sha256:0f1e...c8a2
    

    Bu yaklaşım bir bakım yükü getirir: güvenlik güncellemelerini takip edip aynayı tazelemeniz gerekir. Buna karşılık üretim dağıtımınız artık kendi altyapınızın içinde tamamlanır.

    Çözüm 3: Pull-Through Cache (Registry Mirror) Kurmak#

    Onlarca sunucunuz veya yoğun bir CI hattınız varsa her imajı elle kopyalamak sürdürülebilir değildir. Doğru araç, Docker Hub'ın önüne konan bir pull-through cache'tir: istenen imajı ilk seferde yukarıdan çeker, saklar ve sonraki bütün isteklere yerelden cevap verir. Docker Hub'a giden istek sayısı, sunucu sayısından bağımsız olarak imaj çeşidi kadar iner.

    Resmi registry imajı bu görevi bir proxy bölümüyle üstlenir:

    version: 0.1
    storage:
      filesystem:
        rootdirectory: /var/lib/registry
    http:
      addr: :5000
    proxy:
      remoteurl: https://registry-1.docker.io
      username: dockerhub_kullanici
      password: dockerhub_erisim_jetonu
      ttl: 168h
    
    docker run -d --name hub-mirror --restart=always -p 5000:5000 \
      -v /srv/mirror/config.yml:/etc/distribution/config.yml:ro \
      -v /srv/mirror/data:/var/lib/registry \
      registry:3
    

    Yapılandırma dosyasının konteyner içindeki yolu sürüme göre değişir: registry:3 imajında /etc/distribution/config.yml, eski registry:2 imajında /etc/docker/registry/config.yml. Yanlış yola bağlarsanız konteyner varsayılan ayarlarla açılır ve ayna hiç devreye girmez.

    İstemci tarafında her sunucunun /etc/docker/daemon.json dosyasına aynayı tanıtın:

    {
      "registry-mirrors": ["https://mirror.ornek.com"]
    }
    
    sudo systemctl restart docker
    docker info | grep -A 2 "Registry Mirrors"
    

    Üç uyarı önemlidir. Birincisi, registry-mirrors yalnızca Docker Hub (docker.io) imajları için çalışır; ghcr.io veya özel bir registry'den çekim bu ayardan etkilenmez. İkincisi, aynayı TLS olmadan düz HTTP üzerinden yayınlarsanız istemcilerde ayrıca insecure-registries tanımlamanız gerekir — üretim için doğrusu aynaya sertifika koymaktır. Üçüncüsü ve en önemlisi: yapılandırmaya kullanıcı adı ve parola yazarsanız, o hesabın erişebildiği özel depolar da ayna üzerinden dağıtılır; aynayı kimlik doğrulamayla korumazsanız özel imajlarınızı iç ağa açmış olursunuz.

    Kendi aynanızı işletmek istemiyorsanız Google'ın herkese açık Docker Hub önbelleği sıfır kurulumla aynı işi görür:

    {
      "registry-mirrors": ["https://mirror.gcr.io"]
    }
    

    Çözüm 4: Alternatif Registry'ler ve Resmi İmaj Aynaları#

    Bazı imajların Docker Hub dışında birebir karşılığı vardır ve referansı değiştirmek tek satırlık bir iştir:

    RegistryÖrnek adresNe için uygun
    GitHub Container Registryghcr.io/kurulus/imaj:etiketKendi imajlarınız ve GitHub tabanlı projeler
    Quayquay.io/kurulus/imaj:etiketRed Hat ekosistemi, birçok popüler proje
    AWS Public ECRpublic.ecr.aws/docker/library/nginxResmi Docker imajlarının aynası
    Google aynamirror.gcr.ioYalnızca registry-mirrors olarak kullanılır

    Dikkat edilecek nokta, bu adreslerin de kendi kullanım politikalarının olmasıdır. Alternatif registry, kotayı ortadan kaldırmaz; yalnızca yükü dağıtır. Kritik bağımlılıklar için kalıcı çözüm hâlâ ikinci ve üçüncü bölümlerdir.

    Çekim Sayısını Azaltan Yapı Kararları#

    İmaj tasarımınız da doğrudan kaç istek attığınızı belirler. Birkaç karar, kotayı hiç zorlamadan kalmanızı sağlar:

    • Temel imaj çeşidini azaltın. Altı servisiniz üç farklı temel imaj kullanıyorsa her dağıtımda üç ayrı depoya gidersiniz. Hepsini tek bir temel imajda toplamak hem çekim sayısını hem de derleme süresini düşürür; bu ve benzeri kararlar için Dockerfile en iyi pratikleri yazısına bakın.
    • Gereksiz çekimi kapatın. Compose tarafında pull_policy değerini missing yaparsanız, imaj yereldeyken sürüm kontrolü hiç yapılmaz:
    services:
      web:
        image: nginx:1.27-alpine
        pull_policy: missing
    
    • Etiketleri sabitleyin. :latest yerine 1.27-alpine gibi sabit bir etiket, mümkünse digest kullanın. Compose kullanımının genel kalıpları için Docker Compose ile çok servisli uygulama yazısı iyi bir başlangıçtır.
    • CI'da katman önbelleğini gerçekten kullanın. Her derlemede sıfırdan inen bir temel imaj, kotanın en büyük tüketicisidir. İmaj boyutunu küçültmek de aynı hesaba yazar; Docker imaj boyutu optimizasyonu yazısındaki yöntemler transfer süresini de kısaltır.

    Üretim Kuralı: Kritik Servis Başkasının Kotasına Bağlı Olmamalı#

    Bu hatanın gerçek maliyeti, bir imajın geç inmesi değildir. Gece yarısı bir düğüm yeniden başladığında, otomatik ölçekleme yeni bir sunucu açtığında ya da bir olay sonrası servisi yeniden kurmaya çalıştığınızda ortaya çıkar: konteyner ayağa kalkmaz, çünkü ihtiyaç duyduğu imaj dışarıdan inmelidir ve o dışarısı size şu anda hayır demektedir. Yedeğiniz vardır, yapılandırmanız vardır, ama servis gelmez.

    Bu yüzden ölçüt basittir: üretimde çalışan bir servisin yeniden başlaması, kontrol etmediğiniz bir kotaya bağlı olmamalıdır. Küçük kurulumlar için docker login ile kimlikli çekim çoğu zaman yeterlidir. Birden fazla sunucu, bir küme ya da düzenli çalışan bir CI hattı varsa doğru cevap pull-through cache ya da kendi registry'nizdir. Hangisini seçerseniz seçin, ölçüm alışkanlığını bırakmayın: ratelimit-remaining değerini izlemeye alın ve eşiğin altına düştüğünde uyarı üretin. Kota hatasını dağıtım anında öğrenmek, en pahalı öğrenme biçimidir.

    Sıkça Sorulan Sorular#

    Ücretsiz Docker hesabıyla giriş yapmak limiti gerçekten yükseltir mi?#

    Evet, ve asıl kazanç sayının büyümesi değildir. Anonim çekimlerde kota çıkış IP adresinize yazılır; aynı IP'yi paylaşan herkes aynı sayacı tüketir. Kimlikli çekimde kota hesabınıza taşınır, böylece başkalarının davranışından etkilenmezsiniz. Ücretsiz Docker Personal hesabı bunun için yeterlidir, ücretli plana geçmek zorunda değilsiniz.

    docker login yaptım ama hata devam ediyor, sebebi ne olabilir?#

    En yaygın sebep, girişin farklı bir kullanıcı hesabına yazılmış olmasıdır. docker login kimlik bilgisini onu çalıştıran kullanıcının ev dizinine kaydeder; sonra sudo docker pull çalıştırırsanız root'un yapılandırması okunur ve çekim yine anonim yapılır. Kesin doğrulama için docker-ratelimit-source başlığına bakın: orada bir IP adresi yerine kullanıcı adınızı görmelisiniz.

    Kota dolduğunda ne kadar beklemem gerekiyor?#

    Sayaç kayan bir zaman penceresiyle çalışır, gün başında topluca sıfırlanmaz. Yanıt başlıklarındaki w=21600 değeri pencerenin uzunluğunu saniye cinsinden verir; en eski çekimler pencereden çıktıkça hakkınız kademeli olarak geri gelir. Beklemek yerine kimlikli çekime geçmek her zaman daha hızlı sonuç verir, çünkü sayaç anında değişir.

    Yerelde duran bir imajı çekmek de kotadan düşer mi?#

    Düşebilir. Bir çekim, indirme işleminin yanı sıra sürüm kontrolünü de kapsar; yani etiketin hâlâ aynı içeriği gösterip göstermediğini soran istek de sayılır. Bu yüzden hiçbir şey değişmese bile docker compose pull komutu servis sayısı kadar istek üretir. Compose tarafında pull_policy: missing ayarı, imaj yereldeyken bu kontrolü tamamen atlar.

    Kendi registry aynamı kurmak özel imajlarım için güvenli mi?#

    Yapılandırmayı doğru kurarsanız evet, ama dikkat edilecek bir nokta var. Ayna yapılandırmasına Docker Hub kullanıcı adı ve parolası yazarsanız, o hesabın erişebildiği özel depolar da ayna üzerinden dağıtılabilir hâle gelir. Aynanın kendisini kimlik doğrulamayla korumaz ve iç ağa açık bırakırsanız, özel imajlarınıza erişimi farkında olmadan genişletmiş olursunuz.

    Kubernetes kümesinde bu hatayı nasıl çözerim?#

    docker login burada işe yaramaz, çünkü imajı çeken şey kubelet'in konteyner çalışma zamanıdır. Docker Hub kimlik bilgisini bir Secret olarak tanımlayıp imagePullSecrets ile ServiceAccount'a bağlamanız gerekir. Düğüm sayısı arttıkça her düğümün ayrı çekim yaptığını unutmayın; on düğümün üzerindeki kümelerde doğru çözüm genellikle küme içinde bir pull-through cache çalıştırmaktır.

    DockerRegistryDevOps

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.