Docker pull тормозит или упирается в лимит: что делать

, 6 мин чтения

Медленный docker pull обычно складывается из трёх причин: лимиты реестра, тяжёлые образы и нестабильная сеть. Разберём, как отличить одно от другого и что делать в каждом случае.

Как понять, что именно тормозит

Сначала разделите три случая: отказ реестра, проблема с DNS или соединением, медленная передача. Каждый проверяется одной командой:

time docker pull alpine:latest
docker info | grep -i -A2 'registry mirrors'
curl -s -o /dev/null -w 'connect %{time_connect}  tls %{time_appconnect}\n' https://registry-1.docker.io/v2/

Маленький образ вроде alpine на нормальном канале скачивается за секунды. Если даже он долго висит до начала загрузки слоёв, ищите проблему в DNS, прокси или соединении с реестром. Если старт быстрый, а прогресс по слоям ползёт, дело в пропускной способности или в размере образа. Отказ по лимиту приходит сразу и с понятным текстом, его ни с чем не спутать.

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

Docker Hub ограничивает число скачиваний за период времени. Когда лимит исчерпан, реестр отвечает кодом 429, а docker выводит:

Error response from daemon: toomanyrequests: You have reached your pull rate limit.
You may increase the limit by authenticating and upgrading

В других версиях текст может отличаться, например You have reached your unauthenticated pull rate limit, но ключевое слово всегда toomanyrequests. Это не сбой сети: реестр отказал намеренно, и повтор через минуту не поможет.

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

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, ratelimit-remaining и docker-ratelimit-source. Последний показывает, по какому адресу или аккаунту вас считают. Если заголовков нет, для вашего аккаунта или адреса лимит не действует.

Как устроен лимит

  • Считаются запросы манифеста образа, а не отдельные слои. Для образа под несколько архитектур каждая скачанная архитектура считается отдельно.
  • Анонимные запросы считаются по IPv4-адресу или по подсети IPv6 /64. Всё, что выходит в интернет через один адрес, делит общий лимит: офис, CI-раннеры за NAT, общий хостинг.
  • Для авторизованных пользователей лимит выше и считается по аккаунту, на платных подписках ограничения мягче.
  • Лимит касается только Docker Hub. У ghcr.io, quay.io, public.ecr.aws и реестров облаков свои правила.
  • Точные цифры Docker уже несколько раз менял. Проверьте актуальное значение в документации Docker, раздел «Usage and limits».

Авторизация, в том числе в CI

Самое простое решение: docker login с токеном доступа вместо пароля. Токен создаётся в настройках аккаунта Docker Hub, ему можно дать права только на чтение.

echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USER" --password-stdin

Флаг --password-stdin не оставляет токен в истории оболочки и в списке процессов. В CI храните токен в секретах пайплайна, в GitHub Actions для этого есть шаг docker/login-action. Если раннеры создаются заново для каждой задачи, логин нужен в начале каждой задачи, иначе запросы снова станут анонимными.

Указывайте в CI конкретный тег или дайджест (image@sha256:...) вместо latest. Сборки станут повторяемыми, а закэшированный на раннере образ не придётся перекачивать из-за того, что кто-то обновил плавающий тег.

В Kubernetes образы тянут сами узлы, поэтому учётные данные передают через imagePullSecrets. Заодно проверьте imagePullPolicy: с Always каждый запуск пода обращается к реестру, а IfNotPresent использует образ, который уже лежит на узле.

Зеркала и кэширующие прокси

Для популярных образов с Docker Hub можно указать зеркало в /etc/docker/daemon.json:

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

После изменения перезапустите демон: sudo systemctl restart docker. В выводе docker info должен появиться раздел Registry Mirrors. Зеркало работает только для образов Docker Hub. Если нужного образа в нём нет, демон обратится к Docker Hub напрямую.

  • Docker Desktop на macOS и Windows: тот же JSON редактируется в Settings, раздел Docker Engine, затем Apply & restart.
  • Docker в rootless-режиме на Linux читает ~/.config/docker/daemon.json.
  • Для команды надёжнее свой кэширующий прокси: proxy cache в Harbor, Dependency Proxy в GitLab, pull through cache в AWS ECR, удалённый репозиторий в Google Artifact Registry. Образ скачивается из Docker Hub один раз, дальше все берут его из кэша.
  • Официальные образы Docker также публикуются в ECR Public по пути public.ecr.aws/docker/library/, например public.ecr.aws/docker/library/python:3.12-slim. Для других проектов проверьте, нет ли образа в ghcr.io или quay.io.

Ошибки сети при загрузке

СообщениеЧто значит
net/http: TLS handshake timeoutСоединение с реестром установилось, но шифрованный канал не успел подняться. Перегруженный или нестабильный канал
Client.Timeout exceeded while awaiting headersРеестр не ответил вовремя. Проблема в сети или в прокси
unexpected EOFСлой оборвался посреди загрузки
dial tcp: lookup registry-1.docker.io ... no such hostНе работает DNS на машине, где запущен демон
i/o timeoutПакеты уходят, но ответы не возвращаются. Часто межсетевой экран или маршрут

Важная деталь: образы скачивает не команда docker, а демон dockerd. Переменная HTTPS_PROXY в вашем терминале на него не действует. Прокси для демона задают в daemon.json через ключ proxies или в drop-in файле systemd для службы docker.

Docker Desktop на macOS и Windows запускает демон внутри виртуальной машины. Прокси для него задаётся в Settings, раздел Resources, затем Proxies. Если в системе включён корпоративный прокси, а в Docker Desktop нет, браузер будет работать, а docker pull упадёт по таймауту.

На Linux логи демона удобно смотреть прямо во время загрузки: journalctl -u docker.service -f. Для подробностей включите "debug": true в daemon.json.

Обрывы на больших слоях

Если unexpected EOF и таймауты появляются на тяжёлых слоях, уменьшите число параллельных загрузок и увеличьте число повторов в daemon.json:

{
  "max-concurrent-downloads": 2,
  "max-download-attempts": 10
}

По умолчанию демон качает три слоя одновременно и делает до пяти попыток на каждый. На слабом канале меньше параллельных потоков часто дают более стабильный результат. Чтобы понять, какой слой самый тяжёлый, посмотрите docker history <образ>. Если вы поднимаете сразу много сервисов, docker compose pull качает их образы параллельно, и на слабом канале надёжнее тянуть по одному: docker compose pull web.

Если TLS handshake timeout стабильно повторяется в одной сети и пропадает в другой, проверьте MTU. На Linux ping -M do -s 1472 8.8.8.8, на macOS ping -D -s 1472 8.8.8.8 отправляют пакеты полного размера с запретом фрагментации. Если они не проходят, а с меньшим -s проходят, крупные пакеты по пути теряются, и рукопожатие TLS зависает.

Кэш слоёв при сборке

  • Копируйте файлы зависимостей отдельно от исходников: сначала COPY package*.json ./ и установка, потом COPY . .. Тогда правка кода не сбрасывает слой с зависимостями.
  • Используйте кэш BuildKit: RUN --mount=type=cache,target=/root/.npm npm ci.
  • Берите облегчённые базовые образы (-slim, -alpine), если проект с ними совместим.
  • Добавьте .dockerignore с node_modules, .git и артефактами сборки: контекст меньше, кэш стабильнее.
  • Многоэтапная сборка (FROM ... AS build) оставляет в итоговом образе только нужное, и серверам меньше качать.
  • В CI сохраняйте кэш между запусками: docker buildx build --cache-from type=registry,ref=<образ>:cache --cache-to type=registry,ref=<образ>:cache,mode=max .
  • Осторожнее с очисткой: docker builder prune удаляет кэш сборки, и следующая сборка снова скачает базовые образы и все зависимости.

Если упирается в маршрут

Если лимит не исчерпан, зеркало настроено, а слои всё равно обрываются, проблема в маршруте до реестра. Стабильное защищённое подключение через ближайшую европейскую локацию обычно убирает обрывы на тяжёлых слоях.

Коротко

  • toomanyrequests означает лимит Docker Hub: выполните docker login с токеном, в CI тоже.
  • Остаток лимита проверяйте запросом HEAD, он не тратит скачивания.
  • Подключите mirror.gcr.io или свой кэширующий прокси.
  • Прокси для dockerd задаётся в daemon.json или systemd, а не в терминале.
  • На нестабильном канале снизьте max-concurrent-downloads и поднимите max-download-attempts.
  • Ускоряйте сборку кэшем BuildKit, .dockerignore и многоэтапными Dockerfile.

Стабильный канал для работы

Один ключ, 20 стран, до 5 устройств на Standard. Возврат в течение 7 дней.

Все тарифы