- LoopX
- داکر از صفر تا حرفهای
- داکر در ایران
- محدودیت Docker Hub و راهحلها
محدودیت 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): چهار علت کاملاً متفاوت.
نام کامل image
Section titled “نام کامل image”وقتی فقط alpine مینویسی، داکر کامل آن را docker.io/library/alpine:latest میخواند.
مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: یک pull سالم
Section titled “مثال ۱: یک pull سالم”docker pull alpine 2>&1 | tail -4latest: Pulling from library/alpineDigest: sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6Status: Image is up to date for alpine:latestdocker.io/library/alpine:latestخطهای آخر یک pull موفق: Digest (شناسهی ثابت محتوا)، Status و نام کامل image. اگر این را میبینی، شبکه و registry مشکلی ندارند و هر خطای دیگری از اشتباه تو یا registry دیگری است.
مثال ۲: tag اشتباه: not found / manifest unknown
Section titled “مثال ۲: tag اشتباه: not found / manifest unknown”docker pull alpine:no-such-tag 2>&1 | tail -2Error response from daemon: failed to resolve reference "docker.io/library/alpine:no-such-tag": docker.io/library/alpine:no-such-tag: not foundregistry در دسترس است (جواب داد!)، ولی چنین tagی ندارد. در نسخههای جدید داکر متن بهصورت ...: not found است؛ در نسخههای قدیمیتر manifest unknown مینوشت (همان کد خطای registry). علت: غلط تایپی در tag یا نسخهای که وجود ندارد. راهحل: tagهای موجود را در صفحهی image در Docker Hub ببین.
مثال ۳: repository اشتباه: pull access denied
Section titled “مثال ۳: repository اشتباه: pull access denied”docker pull lx-no-such-user/no-such-image 2>&1 | tail -2Using default tag: latestError 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”docker pull registry.invalid/team/app:1.0 2>&1 | tail -1 | sed -E 's/ on [0-9.]+:53//' | cut -c1-230Error 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”docker pull localhost:5999/team/app:1.0 2>&1 | tail -1 | cut -c1-230Error 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 یعنی شبکه سالم است؛ مشکل از نام یا tag است.
مثال ۶: 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 حرف میزند:
docker rm -f lx-reg >/dev/null 2>&1docker run -d --name lx-reg registry:2 >/dev/null 2>&1sleep 2REG=$(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 اعلام کن:
REG=$(docker inspect --format '{{(index .NetworkSettings.Networks "bridge").IPAddress}}' lx-reg)echo "{\"insecure-registries\":[\"$REG:5000\"]}" > /etc/docker/daemon.jsonkill -HUP $(pidof dockerd); sleep 3docker pull alpine >/dev/null 2>&1docker tag alpine $REG:5000/app:1.0docker push $REG:5000/app:1.0 >/dev/null 2>&1 && echo "push موفق شد"docker rmi $REG:5000/app:1.0 >/dev/null 2>&1docker 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 یک معنی دارد:
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.ioprobe registry.invalidprobe localhost:5999registry-1.docker.io → در دسترس (HTTP 401)registry.invalid → DNS: نام پیدا نشدlocalhost:5999 → اتصال رد شد (پورت بسته یا فایروال)401 از Docker Hub یعنی «زنده است و احراز هویت میخواهد»؛ این موفقیت است. اگر curl از ترمینال همینجا ۶ یا ۲۸ میدهد، مشکل شبکه است و تغییر نام image کمکی نمیکند.
مثال ۸: خطاهای وابسته به شبکه
Section titled “مثال ۸: خطاهای وابسته به شبکه”بعضی خطاها فقط در شرایط شبکهی خاص رخ میدهند و نتیجه روی شبکهی تو ممکن است فرق کند. یک نمونهی واقعی که هنگام نوشتن این درس روی شبکهی خودم گرفتم: pull از یک registry دیگر (نه Docker Hub):
docker pull gcr.io/distroless/static-debian12 2>&1 | tail -1 | cut -c1-200Error 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 |
مقایسهی راهحلها
Section titled “مقایسهی راهحلها”| راهحل | چه وقت | بدی | درس |
|---|---|---|---|
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 قرار میگیرد؛ پس نظریاً میتواند محتوایی متفاوت از اصل بدهد. چهار قاعده:
- فقط HTTPS (و گواهی معتبر).
insecure-registriesرا برای میرور عمومی استفاده نکن. - digest بدهی برای image های مهم:
image@sha256:...(مثال بعد). - رمز Docker Hub خودت را به میرور نده (
docker loginبه آدرس میرور نزن). - میرور را از ارائهدهندهی قابلاعتماد و رسمی (دانشگاه، شرکت، یا خودت) بگیر و image های حساس را بعد از دریافت اسکن کن (درس اسکن آسیبپذیری).
مثال ۹: pin کردن با digest
Section titled “مثال ۹: pin کردن با digest”هر image یک digest دارد: هش SHA-256 محتوای manifest آن. tag میتواند جابهجا شود (latest فردا چیز دیگری است) ولی digest فقط یک محتوا را نشان میدهد. وقتی با digest pull میکنی، daemon بعد از دانلود هش را حساب میکند و اگر با آنچه خواستهای نخواند، رد میکند، حتی اگر میرور محتوا را عوض کرده باشد.
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 پروژه) میگیری.
پشت پرده
Section titled “پشت پرده”وقتی 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 خودت |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) خطای tag را «مشکل شبکه» فرض کردن
Section titled “۱) خطای tag را «مشکل شبکه» فرض کردن”مثال ۲: not found / manifest unknown یعنی شبکه سالم است. راهحل: اول متن را بخوان و tag را بررسی کن.
۲) میرور بدون تست
Section titled “۲) میرور بدون تست”یک میرور مرده کل pull ها را کند یا خراب میکند (درس میرور، مثال ۶). راهحل: قبل از گذاشتن با curl https://MIRROR/v2/ تستش کن.
۳) insecure-registries برای میرور عمومی
Section titled “۳) insecure-registries برای میرور عمومی”مثال ۶ نشان داد چرا. راهحل: برای میرور عمومی هرگز؛ فقط برای registry داخلی یا آزمایشی.
۴) تکیه بر latest
Section titled “۴) تکیه بر latest”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 در دسترس است). ساختن خطا:
docker pull alpine:3.99 2>&1 | tail -1 | cut -c1-120Error 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.
دیدن جواب
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 alpinediagnose registry.invalid/a/bdiagnose localhost:5999/xalpine [registry-1.docker.io] → در دسترس (HTTP 401)registry.invalid/a/b [registry.invalid] → مشکل DNSlocalhost:5999/x [localhost:5999] → اتصال رد شدimage alpine را با digest اجرا کن (docker run --rm alpine@sha256:... echo ok) و ثابت کن که نام با digest همان image محلی است (IDها برابرند). سپس نشان بده با digest اشتباه چه خطایی میگیری.
دیدن جواب
D=$(docker image inspect alpine --format '{{index .RepoDigests 0}}')docker run --rm "$D" echo oka=$(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…آزمونک
Section titled “آزمونک”خطای «not found» (یا manifest unknown) هنگام pull چه چیزی را نشان میدهد؟
جواب دادن registry یعنی شبکه سالم است.
چرا docker pull فقط با alpine کار میکند؟
registry و namespace و tag پیشفرض دارند.
خطای «server gave HTTP response to HTTPS client» یعنی…
گواهی TLS بگذار؛ insecure فقط برای شبکهی خصوصی.
digest چه تضمینی میدهد که tag نمیدهد؟
tag قابل جابهجایی است؛ digest هش محتواست.
کدام برای میرور شخص ثالث توصیه نمیشود؟
رمزت را به سرویس ناشناس نده.
خروجی curl با rc=6 چه معنی دارد؟
۶: DNS؛ ۷: اتصال رد شد؛ ۲۸: timeout.
جمعبندی
Section titled “جمعبندی”- نام کامل:
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 alpine | pull از 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 / 28 | DNS / اتصال رد شد / timeout |
docker info | grep -A2 Mirrors | میرورهای فعال |
{"insecure-registries":["host:5000"]} | فقط برای registry داخلی بدون TLS |