Docker и DevOps 11 мин чтения 2026-09-20

Собственное кэширующее зеркало Docker Hub на VDS: обход ошибки 403 Forbidden и ускорение загрузки образов

Развертывание собственного pull-through кэширующего реестра Docker Registry на VDS в Docker Compose: решение проблемы 403 Forbidden от Docker Hub, настройка Nginx Reverse Proxy с SSL Let's Encrypt, авторизация через htpasswd, подключение в daemon.json и автоматическая очистка старых слоев.

Инженерная редакция SysKit Проверено инженерами SysKit.ru

Блокировка доступа к официальному реестру Docker Hub с российских IP-адресов стала одной из самых частых причин аварийных сбоев при деплое проектов и сборке образов в CI/CD. Ошибка error pulling image configuration: download failed: 403 Forbidden парализует работу команд: не скачиваются даже базовые системные образы (Ubuntu, Debian, Alpine, Nginx, PostgreSQL, Node.js), а привычная команда docker compose up -d завершается аварийным сбоем.

Попытки использовать открытые публичные зеркала (Yandex Mirror, сторонние прокси) дают лишь временное облегчение: они часто перегружены, искусственно занижают скорость передачи слоев, упираются в жесткие лимиты запросов (rate limits) или вовсе перестают отвечать в самый неподходящий момент. Кроме того, публичные провайдеры могут удалять редко запрашиваемые теги.

Надежное инженерное решение — развертывание собственного pull-through кэширующего прокси-реестра (на базе официального образа registry:2) на независимом виртуальном сервере (VDS) с прямым доступом к международной сети. В этом материале подробно разбираем развертывание реестра в Docker Compose, настройку Nginx Reverse Proxy с бесплатным SSL-сертификатом, разграничение прав доступа, интеграцию в клиентские демоны Docker и автоматическую очистку устаревших слоев.

1. Причины ошибки 403 Forbidden в Docker Hub: геоблокировки и лимиты публичных зеркал

С мая 2024 года компания Docker Inc. ввела принудительные географические ограничения на обслуживание входящих запросов к эндпоинтам registry-1.docker.io, auth.docker.io и production.cloudflare.docker.com. Если запрос поступает с IP-адреса, входящего в российские диапазоны автономных систем (ASN), шлюз Cloudflare возвращает HTTP-код 403 Forbidden.

Сравним различные способы решения проблемы доступности контейнерных образов:

Параметр сравнения Прямой Docker Hub Публичные зеркала Свой Registry-прокси на VDS
Статус доступа в РФ Блокировка 403 Forbidden Нестабилен, частые таймауты 100% стабильный доступ 24/7
Скорость загрузки слоев Низкая (или недоступно) Ограничена провайдером зеркала Максимальная скорость порта (до 1 Гбит/с)
Лимиты скачивания (Rate Limit) 100 pull / 6 часов на анонимный IP Общий пул на всех клиентов зеркала Кэш на диске: 0 обращений к апстриму
Редкие и специфичные теги Доступны все Часто отсутствуют в кэше Автоматический pull любого публичного образа
Безопасность и контроль Зависимость от вендора Риск подмены слоев третьими лицами Полный контроль над хранилищем и трафиком
Стоимость обслуживания Бесплатно (с ограничениями) Бесплатно От ~300–400 ₽/мес за VDS

Собственный реестр работает по принципу pull-through кэша: когда любой из ваших серверов запрашивает образ (например, docker pull postgres:16-alpine), прокси проверяет наличие слоев в своем локальном хранилище на NVMe. Если слоя нет — он прозрачно скачивает его из Docker Hub, сохраняет на диск и отдает клиенту. Все последующие запросы этого же слоя с любых ваших серверов отдаются мгновенно из локального кэша со скоростью до 1 Гбит/с, вообще не расходуя квоты Docker Hub.

2. Системные требования к VDS и выбор оптимальной геолокации

Сервис registry:2 написан на языке Go, отличается высокой оптимизацией и крайне скромным потреблением вычислительных ресурсов:

  • Процессор: 1–2 виртуальных ядра (vCPU). Нагрузка на процессор возникает только в моменты параллельного вычисления SHA256-хэшей скачиваемых слоев.
  • Оперативная память: 1–2 ГБ RAM. Сам демон реестра потребляет всего 50–80 МБ RAM в покое и до 250–400 МБ при активных параллельных загрузках.
  • Дисковое пространство: 30–60 ГБ NVMe SSD. Это главный параметр: размер накопителя определяет, сколько уникальных слоев и базовых образов сможет одновременно храниться в локальном кэше.
  • Сеть: гарантированный канал от 100 Мбит/с (в идеале 1 Гбит/с) без ограничений на объем входящего и исходящего трафика.
  • Геолокация сервера: для прямого взаимодействия с Docker Hub требуется VDS с публичным IP-адресом вне зоны геоблокировок (например, локации в Казахстане, Нидерландах, Германии, Финляндии или Турции).

Совет инженера: проверка геолокации и открытых портов VDS

Перед началом настройки проверьте страну и организацию выданного вам IP-адреса через сервис Определить мой IP-адрес. Также убедитесь, что провайдер не фильтрует входящие сетевые пакеты по портам 80 и 443 с помощью утилиты Сканер открытых портов онлайн на платформе SysKit.

Подключитесь к арендованному серверу по SSH и установите последние обновления операционной системы вместе с Docker Engine:

# Обновление индекса пакетов и компонентов ОС
sudo apt update && sudo apt upgrade -y

# Установка системных утилит
sudo apt install -y curl ca-certificates gnupg lsb-release apache2-utils

# Добавление репозитория Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Установка Docker и плагина Compose
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker

3. Конфигурация кэширующего реестра registry:2 в Docker Compose

Создадим рабочую директорию проекта /opt/docker-registry и подкаталог для хранения кэшированных слоев образов:

sudo mkdir -p /opt/docker-registry/data
cd /opt/docker-registry

Главный файл конфигурации демона реестра — config.yml. Создадим его со строгими директивами кэширования, включенным удалением слоев для сборщика мусора и таймаутами:

cat <<'EOF' | sudo tee /opt/docker-registry/config.yml
version: 0.1
log:
  fields:
    service: registry
  level: info

storage:
  cache:
    blobdescriptor: inmemory
  filesystem:
    rootdirectory: /var/lib/registry
  delete:
    enabled: true
  maintenance:
    uploadpurging:
      enabled: true
      age: 168h
      interval: 24h

http:
  addr: :5000
  headers:
    X-Content-Type-Options: [nosniff]

proxy:
  remoteurl: https://registry-1.docker.io
  username: ""
  password: ""
EOF

Совет инженера: опциональная авторизация в Docker Hub

Если у вас есть платный (Pro/Team) или даже бесплатный персональный аккаунт Docker Hub, укажите ваш логин в username и персональный токен доступа (Personal Access Token) в password. Это повысит суточный лимит обращений к апстриму до 200–5000 скачиваний в сутки.

Теперь сформируем файл оркестрации /opt/docker-registry/docker-compose.yml:

services:
  registry:
    image: registry:2
    container_name: docker-registry-mirror
    restart: unless-stopped
    ports:
      - "127.0.0.1:5000:5000"
    environment:
      REGISTRY_STORAGE_DELETE_ENABLED: "true"
    volumes:
      - ./config.yml:/etc/docker/registry/config.yml:ro
      - ./data:/var/lib/registry
    mem_limit: 512m
    cpus: 1.5
    deploy:
      resources:
        limits:
          cpus: '1.5'
          memory: 512M
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Важное предупреждение: изоляция порта 5000

Обратите внимание на строку 127.0.0.1:5000:5000. Порт реестра привязан строго к локальному петлевому интерфейсу (loopback), что исключает несанкционированный доступ извне в обход правил Nginx. Подробнее о механизме проброса портов читайте в материале Обход правил UFW в Docker: исправление уязвимости публикации портов.

Запустите контейнер реестра:

sudo docker compose up -d
sudo docker compose ps

4. Рекомендуемые надежные VDS-провайдеры для размещения реестра

Для стабильного функционирования кэширующего реестра критически важны два фактора: отсутствие блокировок внешних IP-адресов со стороны Docker Hub и высокая скорость чтения/записи локального диска при одновременной отдаче тяжелых слоев. Ниже представлены надежные провайдеры с зарубежными и российскими локациями:

Рекомендуемые VDS-провайдеры

Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с

Выбор редакции
⚡

Timeweb Cloud

Оптимальный выбор для кэширующего зеркала: большой выбор зарубежных локаций (Казахстан, Нидерланды, Польша) с прямым доступом к Docker Hub, быстрые NVMe-диски со скоростью до 3500 МБ/с и порт до 1 Гбит/с.

Конфигурация
1–2 vCPU / 1–2 GB RAM / 30–50 GB NVMe
Tier III ЦОД
🔷

Selectel

Облачные серверы корпоративного класса с безупречным аптаймом 99.98% и защитой от сетевых атак. Отличный вариант для размещения внутреннего корпоративного реестра с гарантированной пропускной способностью.

Конфигурация
1–2 vCPU / 2 GB RAM / 30–40 GB NVMe
Простой старт
🟠

Beget

Надежный хостинг с интуитивным управлением, мгновенным запуском Ubuntu 24.04 LTS за 60 секунд, щедрым включенным трафиком и круглосуточной службой технической поддержки инженеров.

Конфигурация
1 vCPU / 1–2 GB RAM / 30 GB NVMe

5. Настройка Nginx Reverse Proxy и выпуск SSL Let's Encrypt

Клиентские демоны Docker по умолчанию категорически отказываются взаимодействовать с реестрами по незащищенному протоколу HTTP (требуя добавления флага insecure-registries, что небезопасно и неудобно на большом парке серверов). Настроим веб-сервер Nginx в качестве шлюза с автоматическим шифрованием HTTPS.

Установите Nginx и утилиту Certbot:

sudo apt install -y nginx certbot python3-certbot-nginx

Создайте конфигурационный файл /etc/nginx/sites-available/registry.conf, указав ваш поддомен (например, mirror.yourdomain.ru):

server {
    listen 80;
    listen [::]:80;
    server_name mirror.yourdomain.ru;

    # Критически важно для передачи тяжелых образов Docker
    client_max_body_size 0;
    chunked_transfer_encoding on;

    location / {
        # Проверка спецификации Docker Registry API v2
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Отключение буферизации для потоковой передачи гигабайтных слоев
        proxy_buffering off;
        proxy_request_buffering off;
        proxy_read_timeout 900s;
        proxy_send_timeout 900s;
    }
}

Активируйте виртуальный хост, протестируйте синтаксис и перезагрузите Nginx:

sudo ln -s /etc/nginx/sites-available/registry.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Выпустите бесплатный сертификат Let's Encrypt через Certbot:

sudo certbot --nginx -d mirror.yourdomain.ru --agree-tos --no-eff-email -m admin@yourdomain.ru

Проверить срок действия и безопасность созданного SSL-сертификата можно в онлайн-инструменте Проверка SSL / TLS сертификата на SysKit.

6. Аутентификация и защита реестра от несанкционированного доступа

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

Существует два надежных способа защиты реестра:

Вариант 1: Ограничение доступа по доверенным IP-адресам (Рекомендуется)

Если у ваших целевых серверов статические IP-адреса, добавьте директивы allow и deny прямо в блок location / конфигурации Nginx:

location / {
    # Доверенные IP-адреса ваших серверов разработки и продакшена
    allow 198.51.100.42;
    allow 203.0.113.0/24;
    deny all;

    proxy_pass http://127.0.0.1:5000;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Вариант 2: Базовая HTTP-аутентификация через htpasswd

Если IP-адреса клиентов динамические, создайте защищенный файл паролей с помощью алгоритма bcrypt:

sudo htpasswd -B -c /etc/nginx/registry.htpasswd devops_user

И подключите проверку авторизации в блоке Nginx:

location / {
    auth_basic "Docker Registry Restricted Area";
    auth_basic_user_file /etc/nginx/registry.htpasswd;

    proxy_pass http://127.0.0.1:5000;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

После изменения конфигурации примените параметры: sudo nginx -t && sudo systemctl reload nginx.

7. Подключение зеркала на клиентских серверах: настройка daemon.json

Теперь подключим созданное зеркало на всех ваших серверах, где требуется загружать Docker-образы.

Откройте файл конфигурации демона /etc/docker/daemon.json на клиентском сервере (создайте его, если он отсутствует) и добавьте URL вашего зеркала в секцию registry-mirrors:

{
  "registry-mirrors": [
    "https://mirror.yourdomain.ru"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Совет инженера: мягкая перезагрузка демона Docker

Чтобы изменения вступили в силу без остановки и прерывания работы запущенных контейнеров, выполните команду перезагрузки конфигурации демона: sudo systemctl reload docker (вместо жесткого restart).

Если на зеркале настроена базовая HTTP-авторизация, предварительно выполните вход на клиентской машине:

docker login mirror.yourdomain.ru -u devops_user

Проверка работы кэширования

Проверим скачивание любого официального образа из командной строки клиента:

# Скачивание образа
docker pull postgres:16-alpine

На сервере зеркала откройте логи контейнера реестра, чтобы убедиться в фиксации запроса:

sudo docker logs -f docker-registry-mirror

В логах отобразятся запросы вида GET /v2/library/postgres/manifests/16-alpine с ответами 200 OK. При повторном скачивании этого же образа с любого другого вашего сервера слои будут отдаваться мгновенно из каталога /opt/docker-registry/data.

8. Автоматическая очистка устаревших слоев и ротация кэша (Garbage Collection)

По мере работы размер кэша на диске будет расти. Когда разработчики собирают и скачивают десятки различных версий образов, накопитель VDS может заполниться на 100%. Сам по себе Docker Registry не удаляет старые файлы с диска автоматически при перезаписи тегов.

Для поддержания свободного места настроим регулярную сборку мусора (Garbage Collection).

Создадим исполняемый скрипт очистки /opt/docker-registry/cleanup.sh:

cat <<'EOF' | sudo tee /opt/docker-registry/cleanup.sh
#!/bin/bash
set -eo pipefail

REGISTRY_DIR="/opt/docker-registry"
LOG_FILE="/var/log/registry-cleanup.log"

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Запуск плановой сборки мусора в Docker Registry..." >> "$LOG_FILE"

# 1. Запуск сборщика мусора внутри контейнера в режиме удаления неподтвержденных блобов
docker exec docker-registry-mirror bin/registry garbage-collect --delete-untagged /etc/docker/registry/config.yml >> "$LOG_FILE" 2>&1

# 2. Вывод текущего размера каталога с данными
CURRENT_SIZE=$(du -sh "$REGISTRY_DIR/data" | cut -f1)
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Сборка мусора успешно завершена. Текущий объем кэша: $CURRENT_SIZE" >> "$LOG_FILE"
EOF

sudo chmod +x /opt/docker-registry/cleanup.sh

Добавим выполнение скрипта в системный планировщик crontab для запуска каждое воскресенье в 04:00 утра:

(sudo crontab -l 2>/dev/null; echo "0 4 * * 0 /opt/docker-registry/cleanup.sh") | sudo crontab -

Сгенерировать точное расписание для других временных интервалов поможет онлайн-инструмент Генератор расписаний Crontab на SysKit, а подробный разбор методов освобождения накопителя читайте в руководстве Закончилось место на диске VDS: поиск пожирателей памяти и безопасная очистка.

9. Чек-лист безопасности и мониторинг дискового пространства

Чтобы сервер зеркала работал автономно и не стал источником проблем, примените финальные настройки сетевой безопасности:

# Базовая настройка межсетевого экрана UFW
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Обязательно настройте регулярную проверку свободного места на корневом разделе VDS. Если объем кэша превысит 85–90% от емкости диска, контейнеры могут перейти в режим read-only. Контролируйте утилизацию командой df -h /opt/docker-registry/data.

10. Резюме и чек-лист готовности к эксплуатации

Собственный pull-through кэширующий реестр на VDS полностью снимает риски геоблокировок Docker Hub, ускоряет процесс развертывания приложений в 3–5 раз и гарантирует независимость вашей инфраструктуры от публичных сбоев.

Перед вводом зеркала в регулярную эксплуатацию сверьтесь с финальным чек-листом:

  • [x] Контейнер registry:2 привязан к локальному порту 127.0.0.1:5000 и закрыт от прямого внешнего доступа.
  • [x] В конфигурации Nginx установлена директива client_max_body_size 0; и отключена буферизация тяжелых запросов.
  • [x] Выпущен и автоматически продлевается SSL-сертификат Let's Encrypt для поддомена зеркала.
  • [x] Настроена защита доступа через белый список доверенных IP или HTTP-авторизацию htpasswd.
  • [x] В клиентском файле /etc/docker/daemon.json прописана директива registry-mirrors.
  • [x] В планировщике cron настроена еженедельная сборка мусора (Garbage Collection) для удаления неиспользуемых слоев.

Готовы навсегда забыть об ошибке 403 при скачивании образов?

Выберите надежный виртуальный сервер с быстрыми NVMe-дисками, зарубежной локацией и широким каналом связи для создания собственного кэширующего реестра.

Вопросы и ответы (FAQ)

Популярные вопросы вебмастеров и сисадминов по данной теме

Будет ли зеркало кэшировать приватные образы из закрытых репозиториев Docker Hub?
По умолчанию режим pull-through кэша в registry:2 предназначен исключительно для публичных образов. При попытке скачать приватный репозиторий через зеркало демон вернет ошибку авторизации. Для безопасной работы с приватными репозиториями рекомендуется скачивать их напрямую через docker login или использовать полноценный реестр уровня Enterprise (например, Harbor), поддерживающий репликацию закрытых проектов.
Что произойдет, если сервер зеркала временно станет недоступен?
Демон Docker на клиентских машинах спроектирован отказоустойчиво: если зеркало из списка registry-mirrors не отвечает по таймауту или возвращает сетевую ошибку, Docker автоматически попытается обратиться к следующему зеркалу в списке либо напрямую к официальному upstream-реестру. Однако если прямой доступ к Docker Hub с вашего IP заблокирован (403 Forbidden), загрузка завершится ошибкой.
Можно ли использовать одно зеркало для кэширования образов из GitHub Container Registry (ghcr.io) или Quay?
Один инстанс registry:2 в режиме proxy может быть привязан только к одному апстрим-реестру (параметр proxy.remoteurl). Если вам требуется кэшировать образы не только из Docker Hub, но и из ghcr.io или quay.io, вы можете запустить рядом второй контейнер registry на отдельном порту (например, 5001) с соответствующим remoteurl и настроить отдельный поддомен в Nginx.

Читайте также в блоге SysKit

Смежные руководства и полезные технические статьи