SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor

docker pull: context deadline exceeded. Причины и зеркала

Почему docker pull падает с context deadline exceeded или TLS handshake timeout: проверка сети без Docker, лимиты Docker Hub, registry-mirrors, k3s и свой кэш.

Что значит context deadline exceeded при docker pull

Ошибка context deadline exceeded при docker pull значит одно: демон Docker отправил запрос в реестр и не дождался ответа за отведённое время. Образ, тег и ваш docker-compose.yml здесь не виноваты. Виноват сетевой путь от сервера до Docker Hub. Ниже описано, как найти место, где обрывается связь, и как поставить между сервером и Docker Hub зеркало, чтобы сборки не зависели от этого пути.

Порядок работы простой. Сначала проверяем сеть без Docker, потом делаем один маленький docker pull, потом настраиваем зеркало для Docker или для k3s. В конце разбираем свой кэш реестра на собственном сервере. Это стандартный способ защититься и от лимитов, и от нестабильной связи.

Какая ошибка о какой причине говорит

Обычно у читателя уже есть конкретная строка из терминала. Найдите свою строку в списке.

  • context deadline exceeded, часто рядом с адресом https://registry-1.docker.io/v2/. Запрос ушёл, ответ не пришёл вовремя. Причина в сети: DNS, маршрут, фильтрация трафика или прокси.
  • TLS handshake timeout. TCP-соединение открылось, но согласование TLS (transport layer security, протокол шифрования соединения) не закончилось вовремя. Это тоже сетевой путь, а не Docker.
  • Ответ 403 со страницей про экспортный контроль США. Это решение самого Docker. Сеть исправна, и настройка клиента это не меняет.
  • Ответ 429 и текст You have reached your pull rate limit. Это лимит скачиваний Docker Hub. Подробно лимиты разобраны в статье о бесплатном Docker Engine, Docker Desktop и лимитах Docker Hub.
  • Скачивание слоёв началось и встало на одном месте. Это отдельный случай, и для него ниже есть только гипотеза с проверкой.

Строка context deadline exceeded встречается не только в Docker. Это общий текст ошибки в программах на Go, когда истекает срок операции. Если вы видите её при загрузке моделей, прочитайте разбор ошибки context deadline exceeded в Ollama: логика поиска причины там похожая.

Шаг 1. Проверьте DNS и доступ к реестру без Docker

Сначала исключите Docker из проверки. Если сам сервер не достаёт до реестра, настройки демона ничего не решат.

getent hosts registry-1.docker.io
curl -sS -o /dev/null -w '%{http_code}\n' --max-time 15 https://registry-1.docker.io/v2/

getent должен напечатать один или несколько IP-адресов. Пустой вывод значит, что имя не разрешается. Тогда проблема в DNS (domain name system, система доменных имён), и смотреть нужно resolvectl status и /etc/resolv.conf.

Для curl здоровый ответ это код 401. По спецификации API реестра запрос к /v2/ получает 401 и заголовок WWW-Authenticate, если реестр требует авторизацию. Значит, реестр жив и просит токен. Из рабочей сети 9 октября 2026 года этот запрос вернул 401. Если же через 15 секунд curl печатает 000 и ошибку (28), значит, истёк таймаут. Это та же проблема, которую видит Docker.

Чтобы понять, на каком шаге встаёт соединение, запустите подробный режим:

curl -v --max-time 15 -o /dev/null https://registry-1.docker.io/v2/

Если вывод обрывается до строки, которая начинается с * Connected to, TCP-соединение не открылось. Если строка * Connected to есть, а дальше вывод замирает на шагах TLS, соединение открылось, но шифрование не согласовалось. Docker называет этот случай TLS handshake timeout.

Шаг 2. Почему curl работает, а docker pull нет

Бывает обратная картина: curl быстро получает 401, а docker pull всё равно падает. Причина обычно в том, что curl и демон dockerd это разные процессы с разным окружением. Переменная HTTPS_PROXY, заданная в вашей оболочке, на демон не действует, потому что демон запускает systemd. Проверьте, что видит сам демон:

sudo systemctl show --property=Environment docker
grep -A4 proxies /etc/docker/daemon.json

Если прокси нужен, документация Docker рекомендует ключ proxies в /etc/docker/daemon.json:

{
  "proxies": {
    "http-proxy": "http://proxy.example.com:3128",
    "https-proxy": "http://proxy.example.com:3128",
    "no-proxy": "127.0.0.0/8"
  }
}

После правки перезапустите демон командой sudo systemctl restart docker. Docker Desktop эти ключи игнорирует, они работают только для Docker Engine на сервере. Как поставить сам Engine на чистый сервер, описано в руководстве по установке Docker на VPS.

Шаг 3. Один маленький docker pull с замером времени

Теперь проверьте сам Docker на самом маленьком образе:

time docker pull hello-world

Здоровый результат занимает секунды и заканчивается строкой Status: Downloaded newer image for hello-world:latest или Status: Image is up to date for hello-world:latest. Если маленький образ скачался, а большой падает или встаёт на середине слоя, переходите к разделу о зависших слоях.

Если вы подозреваете лимит, проверьте его напрямую. Это команды из документации Docker. Им нужен jq (sudo apt install -y jq):

TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest

В ответе ищите заголовки ratelimit-limit и ratelimit-remaining. Значение вида 100;w=21600 значит 100 скачиваний за окно в 21600 секунд, то есть за 6 часов. По документации Docker Hub без входа даёт 100 скачиваний за 6 часов на один IPv4-адрес или на подсеть IPv6 /64. Личная учётная запись после docker login получает 200 за те же 6 часов.

Почему Docker Hub отвечает 403 со ссылкой на экспортный контроль

Договор подписки Docker прямо говорит, что сервисы подчиняются экспортному контролю США. В странах из санкционного списка OFAC (Office of Foreign Assets Control, управление по контролю за иностранными активами Минфина США) доступ к сервисам может быть ограничен.

Хабр 31 мая 2024 года опубликовал текст страницы 403, которую тогда получали пользователи из России. В этом тексте Docker ссылается на экспортный контроль США и перечисляет Кубу, Иран, Северную Корею, Крым, Судан и Сирию. 3 июня 2024 года Хабр сообщил, что hub.docker.com снова доступен из РФ. Это сообщение Хабра. Собственного объявления Docker о блокировке или о её снятии мы не нашли. О текущем статусе доступа из какой-либо страны мы ничего не утверждаем.

Главное практическое следствие: 403 это не сетевая ошибка. Таймауты и DNS здесь ни при чём. Если вы видите 403 с таким текстом, читайте условия Docker, а не настройки сервера.

Слои начинают качаться и замирают: гипотеза для проверки

Cloudflare в своём блоге от 26 июня 2025 года пишет, что с 9 июня 2025 года российские провайдеры замедляют трафик к сайтам за Cloudflare. По словам Cloudflare, пользователь получает только первые 16 КБ любого ресурса, а замедление применяют местные провайдеры.

Через какую CDN (content delivery network, сеть доставки контента) Docker Hub отдаёт слои образов, мы по документации Docker не подтвердили. Поэтому связь между этим замедлением и вашими зависшими слоями остаётся гипотезой. Проверьте её по четырём признакам:

  1. getent и curl из шага 1 отвечают нормально.
  2. Образ hello-world скачивается без ошибок.
  3. Большие слои каждый раз встают в самом начале, на малом объёме.
  4. Тот же docker pull того же тега с сервера в другой стране проходит без задержек.

Если все признаки совпадают, картина похожа на описание Cloudflare. Это всё равно не доказательство. Но практический вывод одинаковый: слои нужно получать другим путём, то есть через зеркало или свой кэш.

Как настроить registry-mirrors в /etc/docker/daemon.json

Зеркало реестра (registry mirror) это сервер, который отдаёт образы Docker Hub вместо самого Docker Hub. Docker указывают на него ключом registry-mirrors:

{
  "registry-mirrors": ["https://mirror.example.com"]
}

Если файл /etc/docker/daemon.json уже есть, добавьте ключ в существующий объект, а не перезаписывайте файл. Иначе пропадут другие настройки, например proxies. Затем перезапустите демон и проверьте результат:

sudo systemctl restart docker
docker info | grep -A2 'Registry Mirrors'

docker info печатает раздел Registry Mirrors: только тогда, когда настроено хотя бы одно зеркало. Если раздела нет, демон не прочитал ключ. Если в JSON ошибка синтаксиса, демон не запустится, и sudo systemctl restart docker вернёт ошибку. Её текст покажет journalctl -u docker -n 30.

Четыре правила, которые пропускают многие инструкции 2024 года.

  1. Адрес зеркала это корень домена. Документация реестра требует URL без пути, без фрагмента и без строки запроса. https://mirror.example.com подходит, адрес с путём после домена нет.
  2. Ключ работает только для Docker Hub. Демон Docker поддерживает зеркала только для Docker Hub. Образы с ghcr.io или quay.io по-прежнему идут напрямую в свой реестр, и registry-mirrors их не касается.
  3. Docker Hub остаётся последним вариантом. В текущем исходном коде moby демон сначала перебирает зеркала, а в конец списка всегда добавляет сам Docker Hub. После ошибки зеркала демон пишет в журнал Attempting next endpoint for pull after error и пробует следующий адрес.
  4. Ключ меняется только с перезапуском. Документация Docker требует перезагрузить демон после правки файла. Планируйте это время, потому что контейнеры на хосте зависят от демона.

У третьего правила есть оговорка. В том же коде функция, которая решает, переходить ли к следующему адресу, возвращает «нет» для ошибок context.Canceled и context.DeadlineExceeded. Значит, зеркало, которое не отвечает до истечения срока, может оборвать скачивание с той же ошибкой context deadline exceeded, и до Docker Hub дело не дойдёт. Мы не проверяли, какую именно ошибку даёт зависшее зеркало. Проверьте это на своём сервере до того, как полагаться на переход к Docker Hub: укажите зеркало, которое не отвечает, скачайте образ и посмотрите journalctl -u docker --since "10 min ago".

Российские хостеры публиковали свои зеркала в 2024 году. Например, Beget в статье базы знаний от 30 мая 2024 года указал адрес https://dockerhub1.beget.com (статья Beget). Мы не проверяли, что это зеркало сегодня отдаёт образы. Публичные зеркала появляются и пропадают, поэтому любой адрес проверяйте сами до того, как пропишете его на все серверы. Запрос curl к /v2/ зеркала должен вернуть 200 или 401, а docker pull hello-world с одним этим зеркалом в списке должен пройти.

Чужое зеркало может отдать любой образ: фиксируйте digest

Зеркало, которым управляет кто-то другой, само решает, какие байты вам отдать. Тег вроде alpine:3.20 это просто имя, и оно может указывать на что угодно. Digest (контрольная сумма sha256 манифеста образа) указывает на одно конкретное содержимое.

Документация Docker описывает это так: скачивание по digest закрепляет образ за одной версией и гарантирует, что образ всегда тот же. Поэтому зеркало не может подменить содержимое образа, который вы скачиваете по digest.

Узнайте digest в сети, которой доверяете, напрямую у Docker Hub:

docker buildx imagetools inspect alpine:3.20

В выводе есть строка Digest: со значением sha256:.... На сервере за зеркалом скачивайте образ по этому значению:

docker pull alpine@sha256:<digest из вывода выше>

В Compose то же самое пишется в поле image: image: alpine@sha256:<digest>. Как устроен сам файл Compose, разобрано в основах Docker Compose на VPS.

У закрепления есть цена, и документация Docker её называет. Образ по digest не получает обновлений, в том числе обновлений безопасности. Обновлять digest нужно самому: регулярно запускать imagetools inspect в доверенной сети и менять значение.

k3s и containerd: зеркало через /etc/rancher/k3s/registries.yaml

k3s не использует dockerd, поэтому daemon.json на него не влияет. Внутри k3s работает containerd. При запуске k3s проверяет, есть ли файл /etc/rancher/k3s/registries.yaml, и строит из него конфигурацию containerd. Если кластер у вас ещё не поднят, начните с руководства по k3s на одном VPS.

На 13 октября 2026 года канал stable в k3s указывает на v1.36.5+k3s1. Более новый v1.37.1+k3s1 пока есть только в канале latest. Документация k3s рекомендует stable для рабочих серверов: его релизы прошли период тестирования сообществом, а релизы latest ещё нет. Конфигурация ниже взята из документации k3s. Закрепите версию при установке, чтобы обновление не пришло неожиданно:

curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.36.5+k3s1 sh -

Затем опишите зеркало для Docker Hub в /etc/rancher/k3s/registries.yaml:

mirrors:
  docker.io:
    endpoint:
      - "https://mirror.example.com"

Изменения вступают в силу только после перезапуска k3s, причём на каждом узле:

sudo systemctl restart k3s
sudo k3s crictl pull docker.io/library/alpine:3.20

Если crictl pull прошёл, containerd получил образ. Учтите одну деталь из документации k3s: crictl image ls показывает образ под именем docker.io/..., даже если он пришёл с зеркала. По имени нельзя понять, откуда пришли байты.

Что делает k3s, когда зеркало не отвечает? containerd пробует адреса из списка endpoint, а затем адрес реестра по умолчанию, и берёт первый рабочий. Значит, Docker Hub остаётся запасным вариантом. Если узел должен качать только с зеркала, запустите k3s с флагом --disable-default-registry-endpoint. Тогда при недоступном зеркале скачивание просто упадёт.

Ещё несколько деталей из документации k3s:

  • Если зеркало работает без TLS, пишите адрес с http://. Без схемы k3s подставит https.
  • Ключ под mirrors это имя реестра. Запись "*" задаёт настройки по умолчанию для всех реестров, и её нужно брать в кавычки. Она применяется, только если для реестра нет своей записи.
  • Логины и сертификаты для зеркала задаются в отдельном разделе configs.
  • Файл читается только при запуске, поэтому после каждой правки снова нужен sudo systemctl restart k3s.

Свой кэш Docker Hub на registry:3

Самый надёжный вариант это зеркало, которым управляете вы сами. Официальный образ реестра умеет работать как pull-through cache, то есть как кэш со сквозным скачиванием. Первый запрос образа реестр передаёт в Docker Hub и сохраняет слои у себя. Следующие запросы он отдаёт с диска. Это стандартный инструмент и против лимитов, и против нестабильной связи.

В инструкциях 2024 года обычно стоит registry:2. Текущая документация реестра использует registry:3, и мы берём его. Кэш нужно ставить на сервер, у которого хорошая связь и с Docker Hub, и с вашими серверами.

docker run -d --name hub-mirror --restart always -p 127.0.0.1:5000:5000 -v hub-mirror:/var/lib/registry -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io registry:3

Переменная REGISTRY_PROXY_REMOTEURL задаёт ключ proxy.remoteurl из конфигурации реестра. Реестр читает любой ключ конфигурации из переменной с префиксом REGISTRY_, поэтому отдельный файл конфигурации не нужен. Порт открыт только на 127.0.0.1, так что сначала кэш виден только с этого сервера. Проверьте его:

curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5000/v2/
time docker pull 127.0.0.1:5000/library/alpine:3.20

Код 200 значит, что реестр отвечает и авторизации не требует. Официальные образы в кэше лежат под префиксом library/. Удалите локальную копию командой docker image rm 127.0.0.1:5000/library/alpine:3.20 и снова выполните docker pull. Второй раз слои придут с диска кэша, а не из Docker Hub.

Чтобы другие серверы могли брать образы из кэша, его нужно открыть наружу. Документация реестра требует для этого TLS. Сертификат и ключ передаются переменными REGISTRY_HTTP_TLS_CERTIFICATE и REGISTRY_HTTP_TLS_KEY, а каталог с ними монтируется в контейнер. Порт откройте в межсетевом экране только для адресов ваших серверов. На клиентах пропишите https://mirror.example.com:5000 в registry-mirrors или в registries.yaml, как в разделах выше.

Несколько настроек лучше решить заранее:

  • Время хранения. По умолчанию кэш хранит образы 168 часов, то есть 7 дней. Ключ proxy.ttl меняет это значение.
  • Очистка. Чтобы реестр сам удалял старые записи, должно быть включено удаление: REGISTRY_STORAGE_DELETE_ENABLED=true.
  • Учётная запись Docker Hub. Логин и пароль задаются переменными REGISTRY_PROXY_USERNAME и REGISTRY_PROXY_PASSWORD. Тогда кэш скачивает с лимитом вашей учётной записи. Но через зеркало становятся доступны и все приватные образы этой учётной записи, поэтому документация реестра прямо требует закрыть такое зеркало авторизацией.
  • Правила Docker. Зеркала Docker Hub по-прежнему подчиняются политике честного использования Docker.

Кэш занимает место на диске, и оно растёт с каждым новым образом. Следите за ним так же, как описано в руководстве по очистке диска Docker на VPS.

FAQ

Почему curl до registry-1.docker.io работает, а docker pull падает с context deadline exceeded?

curl и демон dockerd работают в разном окружении. Прокси, заданный в вашей оболочке, демон не видит, потому что его запускает systemd. Проверьте окружение демона командой sudo systemctl show --property=Environment docker и ключ proxies в /etc/docker/daemon.json. Если маленький образ hello-world скачивается, а большие слои встают, проблема в пути до хранилища слоёв, а не в доступе к API реестра.

Поможет ли registry-mirrors для образов с ghcr.io или quay.io?

Нет. Демон Docker поддерживает зеркала только для Docker Hub, поэтому образы из других реестров идут напрямую. В k3s ситуация другая: в /etc/rancher/k3s/registries.yaml ключ под mirrors это имя реестра, и зеркало описывается для каждого реестра отдельно.

Что будет, если зеркало перестанет отвечать?

В текущем исходном коде moby Docker после ошибки зеркала переходит к следующему адресу, а последним всегда пробует сам Docker Hub. Исключение: при ошибке context deadline exceeded переход не делается, и скачивание может упасть. В k3s containerd пробует зеркала и адрес по умолчанию и берёт первый рабочий, если узел не запущен с --disable-default-registry-endpoint. Проверьте поведение на своём сервере с заведомо неработающим зеркалом.

Как понять, что я упёрся в лимит Docker Hub, а не в сеть?

Лимит даёт ответ 429 и текст You have reached your pull rate limit, а не таймаут. Получите анонимный токен с auth.docker.io и сделайте curl --head к манифесту ratelimitpreview/test. Заголовок ratelimit-remaining покажет, сколько скачиваний осталось в текущем 6-часовом окне.

Безопасно ли брать образы с чужого зеркала?

Зеркало может отдать по тегу любое содержимое. Узнайте digest образа в доверенной сети командой docker buildx imagetools inspect и скачивайте образ как имя@sha256:.... Образ по digest всегда один и тот же, поэтому зеркало не может его подменить. Цена: обновления безопасности не приходят сами, и digest нужно обновлять вручную.

#docker#docker-hub#registry-mirror#troubleshooting#k3s#containerd