رفتن به محتوا
LoopX

محدودیت Docker Hub و راه‌حل‌ها

توی این درس یاد می‌گیری وقتی docker pull خطا می‌دهد، از روی متن خطا بفهمی مشکل کجاست: نام image یا tag اشتباه، registry پیدا نمی‌شود (DNS)، به آن وصل نمی‌شود، پروتکل HTTP/HTTPS نمی‌خواند، یا دسترسی محدود است. بعد راه‌حل‌های موجود (میرور، proxy، registry دیگر، انتقال آفلاین) را کنار هم مقایسه می‌کنی و یاد می‌گیری با digest مطمئن شوی همان image‌ای را گرفته‌ای که می‌خواستی، حتی از یک میرور غیرقابل‌اعتماد.

مسئله: خطا را بخوان، نه حدس بزن

Section titled “مسئله: خطا را بخوان، نه حدس بزن”

docker pull در ایران ممکن است به چند دلیل متفاوت شکست بخورد و هر کدام راه‌حل خودش را دارد. اگر خطا را نخوانی، ممکن است ساعت‌ها میرور عوض کنی در حالی که فقط نام tag را اشتباه نوشته‌ای. پس اول تشخیص، بعد راه‌حل.

تشبیه: پیدا کردن کتاب در کتابخانه

Section titled “تشبیه: پیدا کردن کتاب در کتابخانه”

docker pull ghcr.io/org/app:1.2 مثل این است که بگویی «از کتابخانه‌ی ghcr.io، قفسه‌ی org، کتاب app، چاپ 1.2 را بیاور». اگر جواب بیاید «چنین کتابخانه‌ای نیست» (DNS)، «در بسته است» (اتصال)، «چنین قفسه‌ای نیست» (نام)، یا «چاپ ۱.۲ را نداریم» (tag): چهار علت کاملاً متفاوت.

هر بخش نام یک نوع خطای متفاوت می‌دهد: registry غلط ← خطای DNS/اتصال، namespace/repo غلط ← pull access denied، tag غلط ← manifest unknown.

وقتی فقط alpine می‌نویسی، داکر کامل آن را docker.io/library/alpine:latest می‌خواند.

Terminal window
docker pull alpine 2>&1 | tail -4
خروجی
latest: Pulling from library/alpine
Digest: sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6
Status: Image is up to date for alpine:latest
docker.io/library/alpine:latest

خط‌های آخر یک pull موفق: Digest (شناسه‌ی ثابت محتوا)، Status و نام کامل image. اگر این را می‌بینی، شبکه و registry مشکلی ندارند و هر خطای دیگری از اشتباه تو یا registry دیگری است.

مثال ۲: tag اشتباه: not found / manifest unknown

Section titled “مثال ۲: tag اشتباه: not found / manifest unknown”
Terminal window
docker pull alpine:no-such-tag 2>&1 | tail -2
خروجی
Error response from daemon: failed to resolve reference "docker.io/library/alpine:no-such-tag": docker.io/library/alpine:no-such-tag: not found

registry در دسترس است (جواب داد!)، ولی چنین tagی ندارد. در نسخه‌های جدید داکر متن به‌صورت ...: not found است؛ در نسخه‌های قدیمی‌تر manifest unknown می‌نوشت (همان کد خطای registry). علت: غلط تایپی در tag یا نسخه‌ای که وجود ندارد. راه‌حل: tagهای موجود را در صفحه‌ی image در Docker Hub ببین.

مثال ۳: repository اشتباه: pull access denied

Section titled “مثال ۳: repository اشتباه: pull access denied”
Terminal window
docker pull lx-no-such-user/no-such-image 2>&1 | tail -2
خروجی
Using default tag: latest
Error response from daemon: pull access denied for lx-no-such-user/no-such-image, repository does not exist or may require 'docker login'

با اینکه عبارت «denied» (ردشدن) آمده، معمولاً یعنی «این repository وجود ندارد یا خصوصی است». علت: نام namespace/repo غلط، یا image خصوصی و docker login نکرده‌ای. راه‌حل: نام را درست کن؛ برای خصوصی docker login.

مثال ۴: registry پیدا نمی‌شود: no such host

Section titled “مثال ۴: registry پیدا نمی‌شود: no such host”
Terminal window
docker pull registry.invalid/team/app:1.0 2>&1 | tail -1 | sed -E 's/ on [0-9.]+:53//' | cut -c1-230
خروجی
Error response from daemon: failed to resolve reference "registry.invalid/team/app:1.0": failed to do request: Head "https://registry.invalid/v2/team/app/manifests/1.0": dial tcp: lookup registry.invalid: no such host

نام registry در DNS وجود ندارد (دامنه‌ی .invalid عمداً هیچ‌وقت ثبت نمی‌شود). علت‌ها: غلط تایپی در آدرس، DNS خراب، یا DNS شبکه آن دامنه را نمی‌دهد. راه‌حل: آدرس را بررسی کن؛ با curl https://REGISTRY/v2/ از بیرون داکر هم امتحان کن (مثال ۷).

مثال ۵: registry جواب نمی‌دهد: connection refused

Section titled “مثال ۵: registry جواب نمی‌دهد: connection refused”
Terminal window
docker pull localhost:5999/team/app:1.0 2>&1 | tail -1 | cut -c1-230
خروجی
Error response from daemon: failed to resolve reference "localhost:5999/team/app:1.0": failed to do request: Head "https://localhost:5999/v2/team/app/manifests/1.0": dial tcp [::1]:5999: connect: connection refused

آدرس resolve شد ولی هیچ برنامه‌ای روی آن پورت گوش نمی‌کند. علت‌ها: registry خاموش است، پورت اشتباه است، یا فایروال. راه‌حل: مطمئن شو سرویس registry بالاست (docker ps، ss -tulpn).

⚡ بررسی سریع

خطای «not found» / «manifest unknown» هنگام pull یعنی چه؟

مثال ۶: registry خودت روی HTTP: server gave HTTP response to HTTPS client

Section titled “مثال ۶: registry خودت روی HTTP: server gave HTTP response to HTTPS client”

داکر برای هر registry غیر از localhost، پیش‌فرض HTTPS می‌خواهد. یک registry آزمایشی (روی همان daemon ایزوله‌ی آزمایشی) بالا می‌آوریم که فقط HTTP حرف می‌زند:

Terminal window
docker rm -f lx-reg >/dev/null 2>&1
docker run -d --name lx-reg registry:2 >/dev/null 2>&1
sleep 2
REG=$(docker inspect --format '{{(index .NetworkSettings.Networks "bridge").IPAddress}}' lx-reg)
echo "registry روی $REG:5000 (فقط HTTP)"
docker pull $REG:5000/app:1.0 2>&1 | grep -o 'http: server gave[^"]*'
خروجی
registry روی 172.18.0.2:5000 (فقط HTTP)
http: server gave HTTP response to HTTPS client

علت: registry با HTTP جواب می‌دهد ولی داکر با HTTPS تماس گرفته. راه‌حل درست: برای registry واقعی گواهی TLS بگذار. راه‌حل موقت (فقط شبکه‌ی داخلی امن): آن را در insecure-registries اعلام کن:

Terminal window
REG=$(docker inspect --format '{{(index .NetworkSettings.Networks "bridge").IPAddress}}' lx-reg)
echo "{\"insecure-registries\":[\"$REG:5000\"]}" > /etc/docker/daemon.json
kill -HUP $(pidof dockerd); sleep 3
docker pull alpine >/dev/null 2>&1
docker tag alpine $REG:5000/app:1.0
docker push $REG:5000/app:1.0 >/dev/null 2>&1 && echo "push موفق شد"
docker rmi $REG:5000/app:1.0 >/dev/null 2>&1
docker pull $REG:5000/app:1.0 >/dev/null 2>&1 && echo "pull موفق شد"
docker info 2>/dev/null | grep -A1 'Insecure Registries' | tr -s ' '
خروجی
push موفق شد
pull موفق شد
Insecure Registries:
172.18.0.2:5000

بعد از تنظیم، push و pull از همان registry کار کرد. (برای برگرداندن، فایل را با آرایه‌ی خالی "insecure-registries": [] بنویس؛ مثل میرور، حذف کلید یا فایل در reload اثر ندارد.) ولی insecure-registries یعنی بدون رمزنگاری و بدون تأیید هویت سرور: هرکس وسط راه باشد می‌تواند محتوا را ببیند یا عوض کند. فقط برای آزمایش یا شبکه‌ی کاملاً خصوصی.

مثال ۷: تشخیص بیرون از داکر با curl

Section titled “مثال ۷: تشخیص بیرون از داکر با curl”

برای اینکه بدانی مشکل از داکر است یا از شبکه، مستقیم به API registry (/v2/) وصل شو. هر کد خروج curl یک معنی دارد:

Terminal window
probe() {
code=$(curl -s -o /dev/null -m 10 -w '%{http_code}' "https://$1/v2/" 2>/dev/null); rc=$?
case $rc in
0) echo "$1 → در دسترس (HTTP $code)";;
6) echo "$1 → DNS: نام پیدا نشد";;
7) echo "$1 → اتصال رد شد (پورت بسته یا فایروال)";;
28) echo "$1 → timeout (مسیر شبکه مسدود یا کند)";;
35|60) echo "$1 → خطای TLS/گواهی";;
*) echo "$1 → curl rc=$rc";;
esac
}
probe registry-1.docker.io
probe registry.invalid
probe localhost:5999
خروجی
registry-1.docker.io → در دسترس (HTTP 401)
registry.invalid → DNS: نام پیدا نشد
localhost:5999 → اتصال رد شد (پورت بسته یا فایروال)

401 از Docker Hub یعنی «زنده است و احراز هویت می‌خواهد»؛ این موفقیت است. اگر curl از ترمینال همین‌جا ۶ یا ۲۸ می‌دهد، مشکل شبکه است و تغییر نام image کمکی نمی‌کند.

مثال ۸: خطاهای وابسته به شبکه

Section titled “مثال ۸: خطاهای وابسته به شبکه”

بعضی خطاها فقط در شرایط شبکه‌ی خاص رخ می‌دهند و نتیجه روی شبکه‌ی تو ممکن است فرق کند. یک نمونه‌ی واقعی که هنگام نوشتن این درس روی شبکه‌ی خودم گرفتم: pull از یک registry دیگر (نه Docker Hub):

Terminal window
docker pull gcr.io/distroless/static-debian12 2>&1 | tail -1 | cut -c1-200
خروجی
Error response from daemon: unknown: failed to resolve reference "gcr.io/distroless/static-debian12:latest": unexpected status from HEAD request to https://gcr.io/v2/distroless/static-debian12/manifes

اگر همین خط روی شبکه‌ی تو 403 Forbidden می‌دهد، یعنی registry درخواست را عمداً رد می‌کند (نه اینکه خراب باشد یا tag اشتباه باشد). در این حالت تغییر نام image کمکی نمی‌کند؛ راه‌حل‌ها proxy، میرور (اگر آن registry را پوشش بدهد) یا انتقال آفلاین‌اند. اگر روی شبکه‌ی تو pull موفق شد، یعنی دسترسی باز است. هر دو حالت درست‌اند؛ مهم این است که متن را بخوانی.

خطاهای دیگر که نتوانستم از اینجا بسازم «نمونه» هستند و از مستندات و گزارش‌های رایج کاربران آمده‌اند:

متن خطا (نمونه) یعنی چه راه‌حل
net/http: TLS handshake timeout اتصال شروع شد ولی TLS کامل نشد (مسیر کند یا مسدود) میرور / proxy
context deadline exceeded مهلت تمام شد میرور / proxy / تلاش دوباره
toomanyrequests: You have reached your pull rate limit سقف pull رایگان بدون ورود رد شد docker login یا registry خودت
x509: certificate signed by unknown authority گواهی registry را نمی‌شناسد (گواهی خودامضا یا میرور) CA را به سیستم اضافه کن؛ نه insecure
هر راه‌حل یک ضعف دارد: میرور فقط Docker Hub را پوشش می‌دهد، proxy به یک سرویس بیرونی وابسته است، registry خودت نگهداری می‌خواهد و انتقال آفلاین دستی است.
راه‌حل چه وقت بدی درس
registry-mirrors (میرور) pull از Docker Hub کند یا بسته است به ارائه‌دهنده اعتماد می‌کنی؛ فقط Docker Hub تنظیم میرور
proxy برای daemon شبکه مسیر خروجی را فقط از proxy می‌دهد همه‌ی ترافیک از proxy می‌گذرد درس بعد
registry جایگزین (GHCR، Quay،…) image در جای دیگر هم منتشر شده باید آدرس را بدانی و تغییر بدهی درس بعد
registry خودت (pull-through cache یا خصوصی) تیم یا CI زیاد pull می‌کند نگهداری و فضا درس بعد
docker save و load سرور اینترنت ندارد دستی؛ به‌روزرسانی سخت‌تر درس سوم این بخش

نکته‌های امنیتی میرورها

Section titled “نکته‌های امنیتی میرورها”

یک میرور شخص ثالث بین تو و Docker Hub قرار می‌گیرد؛ پس نظریاً می‌تواند محتوایی متفاوت از اصل بدهد. چهار قاعده:

  1. فقط HTTPS (و گواهی معتبر). insecure-registries را برای میرور عمومی استفاده نکن.
  2. digest بدهی برای image های مهم: image@sha256:... (مثال بعد).
  3. رمز Docker Hub خودت را به میرور نده (docker login به آدرس میرور نزن).
  4. میرور را از ارائه‌دهنده‌ی قابل‌اعتماد و رسمی (دانشگاه، شرکت، یا خودت) بگیر و image های حساس را بعد از دریافت اسکن کن (درس اسکن آسیب‌پذیری).

هر image یک digest دارد: هش SHA-256 محتوای manifest آن. tag می‌تواند جابه‌جا شود (latest فردا چیز دیگری است) ولی digest فقط یک محتوا را نشان می‌دهد. وقتی با digest pull می‌کنی، daemon بعد از دانلود هش را حساب می‌کند و اگر با آنچه خواسته‌ای نخواند، رد می‌کند، حتی اگر میرور محتوا را عوض کرده باشد.

Terminal window
D=$(docker image inspect alpine --format '{{index .RepoDigests 0}}')
echo "digest مرجع: $D" | sed -E 's/(sha256:[0-9a-f]{12})[0-9a-f]+/\1…/'
docker pull "$D" 2>&1 | tail -2 | sed -E 's/(sha256:[0-9a-f]{12})[0-9a-f]+/\1…/'
echo "--- digest اشتباه:"
docker pull alpine@sha256:0000000000000000000000000000000000000000000000000000000000000000 2>&1 | tail -1 | cut -c1-170 | sed -E 's/(sha256:0{12})0+/\1…/g'
خروجی
digest مرجع: alpine@sha256:294b683cb724…
Status: Image is up to date for alpine@sha256:294b683cb724…
docker.io/library/alpine@sha256:294b683cb724…
--- digest اشتباه:
Error response from daemon: failed to resolve reference "docker.io/library/alpine@sha256:000000000000…": docker.io/libr

با digest درست، pull موفق (یا «up to date»)؛ با digest غلط: خطای not found. این هم راهی برای تکرارپذیری (همیشه همان image) و هم برای تأیید یکپارچگی است. digest مرجع را از منبعی قابل‌اعتماد (مثلاً خودت قبلاً از یک شبکه‌ی سالم یادداشت کرده‌ای، یا فایل lock پروژه) می‌گیری.

وقتی docker pull اجرا می‌شود: daemon اول نام را resolve می‌کند (registry، repo، tag) ← یک درخواست HEAD/GET به /v2/<repo>/manifests/<tag> می‌فرستد ← manifest (فهرست لایه‌ها با digest) را می‌گیرد ← هر لایه را با GET /v2/<repo>/blobs/<digest> دانلود می‌کند و هش هرکدام را با digestش می‌سنجد. به همین دلیل خطاها در مرحله‌ی خودشان ظاهر می‌شوند: نام resolve نشود ← no such host؛ اتصال برقرار نشود ← connection refused/timeout؛ manifest نباشد ← manifest unknown؛ دسترسی نباشد ← denied یا ۴۰۱/۴۰۳.

این معماری محتوا-آدرس‌پذیر (content-addressable) دلیل امنیت digest است: آدرس هر بلاب خودِ هش محتوایش است، پس محتوای دستکاری‌شده نمی‌تواند هم آدرس درست داشته باشد و هم هش درست.

جدول مرجع: از خطا به راه‌حل

Section titled “جدول مرجع: از خطا به راه‌حل”
متن خطا علت اصلی اقدام
manifest unknown / not found tag یا digest وجود ندارد tag را درست کن
pull access denied ... may require authorization repo غلط یا خصوصی نام؛ docker login
no such host DNS آدرس registry؛ DNS؛ میرور
connection refused سرویس گوش نمی‌کند آدرس و پورت
server gave HTTP response to HTTPS client registry فقط HTTP TLS بگذار (یا insecure فقط داخلی)
403 Forbidden (مثال ۸) registry درخواست را رد می‌کند proxy / میرور / آفلاین
TLS handshake timeout / deadline exceeded (نمونه) مسیر شبکه میرور / proxy
toomanyrequests (نمونه) سقف pull login / registry خودت

۱) خطای tag را «مشکل شبکه» فرض کردن

Section titled “۱) خطای tag را «مشکل شبکه» فرض کردن”

مثال ۲: not found / manifest unknown یعنی شبکه سالم است. راه‌حل: اول متن را بخوان و tag را بررسی کن.

یک میرور مرده کل pull ها را کند یا خراب می‌کند (درس میرور، مثال ۶). راه‌حل: قبل از گذاشتن با curl https://MIRROR/v2/ تستش کن.

۳) insecure-registries برای میرور عمومی

Section titled “۳) insecure-registries برای میرور عمومی”

مثال ۶ نشان داد چرا. راه‌حل: برای میرور عمومی هرگز؛ فقط برای registry داخلی یا آزمایشی.

Terminal window
docker image inspect alpine --format 'alpine:latest الان یعنی: {{index .RepoDigests 0}}' | sed -E 's/(sha256:[0-9a-f]{12})[0-9a-f]+/\1…/'
خروجی
alpine:latest الان یعنی: alpine@sha256:294b683cb724…

latest فردا ممکن است digest دیگری باشد. راه‌حل: در پروژه‌های جدی tag دقیق نسخه (alpine:3.20) یا digest.

۵) فکر کردن «اینترنت دارم، پس registry هم دارم»

Section titled “۵) فکر کردن «اینترنت دارم، پس registry هم دارم»”

اینترنت عمومی باز است ولی مسیر خاص یک registry مسدود یا کند است (مثال ۷). راه‌حل: با curl همان آدرس را تست کن، نه با باز کردن یک سایت دیگر.

✎ تمرینآسان

خطای زیر را تشخیص بده و بگو علت چیست: Error response from daemon: failed to resolve reference "docker.io/library/alpine:3.99": ...: not found. سپس با یک دستور واقعی همین خطا را بساز.

دیدن جواب

علت: tag 3.99 برای alpine وجود ندارد (registry در دسترس است). ساختن خطا:

Terminal window
docker pull alpine:3.99 2>&1 | tail -1 | cut -c1-120
خروجی
Error response from daemon: failed to resolve reference "docker.io/library/alpine:3.99": docker.io/library/alpine:3.99:
✎ تمرینمتوسط

تمرین اصلی: یک تابع diagnose بنویس که نام یک image بگیرد، registry آن را از نامش پیدا کند (اگر نبود Docker Hub) و با curl به /v2/ آن وضعیت را بگوید: «در دسترس»، «DNS»، «اتصال رد شد»، یا «timeout». روی سه نام تستش کن: alpine، registry.invalid/a/b، localhost:5999/x.

دیدن جواب
Terminal window
diagnose() {
first=${1%%/*}
case "$1" in
*/*) case "$first" in *.*|*:*|localhost) host=$first;; *) host=registry-1.docker.io;; esac;;
*) host=registry-1.docker.io;;
esac
code=$(curl -s -o /dev/null -m 10 -w '%{http_code}' "https://$host/v2/" 2>/dev/null); rc=$?
case $rc in
0) r="در دسترس (HTTP $code)";;
6) r="مشکل DNS";;
7) r="اتصال رد شد";;
28) r="timeout";;
*) r="curl rc=$rc";;
esac
echo "$1 [$host] → $r"
}
diagnose alpine
diagnose registry.invalid/a/b
diagnose localhost:5999/x
خروجی
alpine [registry-1.docker.io] → در دسترس (HTTP 401)
registry.invalid/a/b [registry.invalid] → مشکل DNS
localhost:5999/x [localhost:5999] → اتصال رد شد
✎ تمرینسخت

image alpine را با digest اجرا کن (docker run --rm alpine@sha256:... echo ok) و ثابت کن که نام با digest همان image محلی است (IDها برابرند). سپس نشان بده با digest اشتباه چه خطایی می‌گیری.

دیدن جواب
Terminal window
D=$(docker image inspect alpine --format '{{index .RepoDigests 0}}')
docker run --rm "$D" echo ok
a=$(docker image inspect alpine --format '{{.Id}}')
b=$(docker image inspect "$D" --format '{{.Id}}')
[ "$a" = "$b" ] && echo "شناسه‌ی image با نام و با digest یکی است"
docker run --rm alpine@sha256:1111111111111111111111111111111111111111111111111111111111111111 echo ok 2>&1 | grep -E "Error|not found" | head -1 | cut -c1-110 | sed -E 's/(sha256:1{12})1+/\1…/'
خروجی
ok
شناسه‌ی image با نام و با digest یکی است
docker: Error response from daemon: failed to resolve reference "docker.io/library/alpine@sha256:111111111111…
؟ آزمونک
  1. خطای «not found» (یا manifest unknown) هنگام pull چه چیزی را نشان می‌دهد؟

  2. چرا docker pull فقط با alpine کار می‌کند؟

  3. خطای «server gave HTTP response to HTTPS client» یعنی…

  4. digest چه تضمینی می‌دهد که tag نمی‌دهد؟

  5. کدام برای میرور شخص ثالث توصیه نمی‌شود؟

  6. خروجی curl با rc=6 چه معنی دارد؟

  • نام کامل: registry/namespace/repo:tag؛ هر بخش یک خطای مخصوص دارد.
  • manifest unknown ← tag غلط؛ pull access denied ← repo غلط یا خصوصی؛ no such host ← DNS؛ connection refused ← سرویس بالا نیست؛ HTTP response to HTTPS client ← registry بدون TLS.
  • قبل از تغییر هر تنظیم، با curl https://REGISTRY/v2/ وضعیت شبکه را از بیرون داکر بسنج (۴۰۱ یعنی سالم).
  • راه‌حل‌ها: میرور، proxy، registry جایگزین/خودت، و انتقال آفلاین؛ هر کدام کاربرد و ضعف دارد.
  • امنیت: HTTPS، digest، رمز خودت را به میرور نده و insecure-registries فقط داخلی.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
docker pull alpinepull از Docker Hub (نام کامل: docker.io/library/alpine:latest)
docker pull alpine@sha256:…pull با digest (محتوای ثابت)
docker image inspect IMG --format '{{index .RepoDigests 0}}'digest یک image محلی
curl -s -o /dev/null -w "%{http_code}" https://REGISTRY/v2/تست دسترسی؛ ۴۰۱ یعنی سالم
curl rc 6 / 7 / 28DNS / اتصال رد شد / timeout
docker info | grep -A2 Mirrorsمیرورهای فعال
{"insecure-registries":["host:5000"]}فقط برای registry داخلی بدون TLS