Если у вас в проекте активно используется Docker и CI, вы почти наверняка упирались в скорость скачивания образов. Каждый pipeline снова тянет один и тот же базовый образ с Docker Hub или другого публичного реестра. Это медленно, нестабильно и иногда приводит к падениям сборок при проблемах на стороне внешнего реестра.
Решение — свой docker registry proxy на отдельном облачном VDS: локальный кеш, который прозрачно проксирует обращения к Docker Hub (и не только), хранит образы рядом с вашими раннерами CI и резко ускоряет повторные сборки. Пара бонусов: уменьшение трафика и меньшая зависимость от внешних сервисов.
Что такое docker registry proxy cache и зачем он CI
По сути это Docker Registry в режиме pull-through cache. Когда CI или разработчик делает docker pull образа из внешнего реестра, запрос летит на ваш VDS:
- если образ уже есть в локальном кеше — он отдается мгновенно, без запроса наружу;
- если нет — proxy скачивает его из внешнего реестра, складывает в кеш и отдаёт клиенту.
Для CI это превращается в ощутимый выигрыш. Большинство пайплайнов используют ограниченный набор базовых образов: python:3.12-slim, node:22-alpine, golang:1.23, alpine и т.п. Первый прогон подождёт, зато все последующие сборки будут забирать их из локального кеша за миллисекунды.
Ключевые плюсы для CI/CD и DevOps:
- Ускорение docker build — меньше времени уходит на network I/O, больше — на реальную работу.
- Стабильность — если Docker Hub недоступен, а образ есть в кеше, ваши сборки не ломаются.
- Контроль — можно видеть, какие образы реально используются, и настраивать политики хранения.
- Экономия трафика — особенно заметна, если CI-раннеры крутятся в другом дата-центре или облаке.
Важно: docker registry proxy cache не заменяет полноценный приватный реестр артефактов (Harbor, GitLab Container Registry и т.д.). Это скорее ускоритель и кэш для внешних базовых образов, а не хранилище ваших собственных релизов.
Выбор режима: простой pull-through cache или комбинированный реестр
Есть несколько базовых вариантов архитектуры.
1. Чистый docker registry proxy для Docker Hub
Самый простой случай: поднимаем официальный registry:2 в режиме proxy к registry-1.docker.io. CI и разработчики ходят только к нему.
Плюсы:
- минимум настроек — один контейнер и конфиг;
- простая интеграция с любым CI — достаточно переопределить адрес реестра.
Минусы:
- работает только как зеркало Docker Hub (и ещё некоторых публичных реестров, если настроить);
- для приватных образов из других реестров потребуется отдельная конфигурация.
2. Комбинация: приватный реестр + proxy cache
Более продвинутый вариант — единый реестр, который:
- хранит ваши приватные образы (артефакты CI/CD, релизы);
- одновременно является proxy cache для Docker Hub и других внешних реестров.
Такую схему удобно реализовывать через продвинутые решения (Harbor, GitLab Container Registry с mirror, Quay и т.п.) или через комбинацию нескольких registry:2 с разными proxy-секциями и reverse-proxy (например, nginx) перед ними.
Преимущество — все docker-операции проходят через единое имя хоста, а CI использует одну точку входа и для базовых образов, и для ваших артефактов.

Требования к VDS под docker registry proxy
Под docker registry proxy cache лучше выделить отдельный VDS, чтобы не смешивать его нагрузку с приложениями и базами. Это снизит риски деградации продакшена из-за всплесков в CI.
Главные ресурсы, которые будут нагружены:
- Диск — образы могут занимать десятки и сотни гигабайт, особенно если вы активно обновляете стеки;
- Сеть — и входящий, и исходящий трафик при первом скачивании слоёв;
- Память — в умеренных пределах, но при большом количестве одновременных подключений лучше иметь запас.
Реальные рекомендации зависят от количества проектов и интенсивности CI, но как ориентир:
- 2–4 vCPU достаточно для десятков одновременных pulls;
- 8–16 ГБ RAM дадут запас под кеши и процесс nginx/registry;
- SSD-диск от 100–200 ГБ чтобы начать; далее масштабировать по факту.
Отдельно обратите внимание на доступность: если ваш CI крутится в другой облачной зоне, проверьте задержки и полосу канала до VDS. Бессмысленно поднимать кеш, если он физически дальше от раннеров, чем Docker Hub.
Если вы только строите инфраструктуру и выбираете, где разместить registry cache и CI, имеет смысл сразу подбирать тарифы на VDS для docker-инфраструктуры в том же регионе, где находятся раннеры и основные сервисы.
Базовая установка Docker Registry в режиме proxy
Рассмотрим типичный сценарий: на VDS установлен Docker, и мы хотим быстро поднять registry-подписку на Docker Hub.
Создаём конфиг docker registry
Создадим каталог под конфигурацию и данные:
mkdir -p /opt/registry/data
mkdir -p /opt/registry/config
Создадим файл /opt/registry/config/config.yml со следующим содержимым (минимальный пример pull-through cache для Docker Hub):
version: 0.1
log:
level: info
fields:
service: registry
storage:
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
proxy:
remoteurl: https://registry-1.docker.io
# Важно: ttl управляет временем жизни кеша метаданных, а не слоёв
ttl: 168h
Обратите внимание на несколько моментов:
storage.filesystem.rootdirectory— путь к каталогу, где будут храниться образы;http.addr— порт, на котором слушает registry (5000 по умолчанию);proxy.remoteurl— адрес внешнего реестра (Docker Hub V2 API).
Каталог /var/lib/registry лучше заранее вынести на отдельный диск или раздел с достаточным запасом места.
Запускаем контейнер registry
Запускаем docker registry с монтированием конфигурации и данных:
docker run -d --name registry-proxy -p 5000:5000 -v /opt/registry/config:/etc/docker/registry -v /opt/registry/data:/var/lib/registry registry:2
Проверяем логи:
docker logs -f registry-proxy
Если всё поднялось без ошибок, можно делать первый тестовый pull.
Проверка работы кеша
На той же машине или с другого хоста (который видит VDS по сети) попробуем скачать образ через proxy:
docker pull localhost:5000/library/alpine:latest
Первый pull займёт время — registry скачает все слои с Docker Hub. Логи покажут обращения к registry-1.docker.io. Второй pull того же образа (даже с другого хоста, но через proxy) будет уже быстрым — слои будут отданы из локального кеша.
Добавляем TLS и reverse-proxy (nginx)
В продакшене раздавать docker registry по обычному HTTP на 5000 порту не стоит. Docker-клиент по умолчанию требует HTTPS (кроме localhost), и нам нужно спрятать registry за nginx c TLS.
Базовая схема
Схема проста:
- nginx слушает 443 порт и принимает запросы от docker-клиентов;
- nginx терминит TLS и проксирует запросы на
localhost:5000, где крутитсяregistry:2; - в docker-клиентах мы настраиваем реестр как
registry.example.com.
Пример конфигурации nginx для docker registry proxy
Ниже пример простого виртуального хоста nginx (без специфичных для какого-либо провайдера директив):
server {
listen 443 ssl http2;
server_name registry.example.com;
ssl_certificate /etc/ssl/certs/registry.crt;
ssl_certificate_key /etc/ssl/private/registry.key;
client_max_body_size 0;
chunked_transfer_encoding on;
location / {
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;
}
}
Важно выставить client_max_body_size 0, иначе push больших слоёв будет падать по ограничению размера тела запроса.
Для публичного registry proxy имеет смысл использовать валидные SSL-сертификаты для контейнерных реестров. Самоподписанные сертификаты потребуют настройки доверия на всех раннерах и developer-хостах, проще сразу купить нормальный сертификат — пригодится и для других сервисов.
Интеграция docker registry proxy с CI
Самая интересная часть — как заставить ваши пайплайны использовать локальный кеш. Рассмотрим два популярных варианта: GitLab CI и GitHub Actions. Принципы для других систем (Jenkins, Drone, TeamCity) аналогичны.
GitLab CI
В GitLab CI типичный билд выглядит так:
image: docker:git
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
build:
stage: build
script:
- docker pull node:22-alpine
- docker build -t app:latest .
Чтобы использовать docker registry proxy, есть два подхода.
1. Прямое указание реестра в имени образа
Меняем имя образа с:
docker pull node:22-alpine
на:
docker pull registry.example.com/library/node:22-alpine
Проблема в том, что стандартные образы Docker Hub вида node:22-alpine обычно резолвятся как docker.io/library/node:22-alpine. Чтобы proxy корректно работал в режиме pull-through, он ожидает именно такой путь. В простейшем варианте он автоматически подхватит /library/..., но вам придётся следить за путями.
2. Настройка daemon.json на раннерах (предпочтительный)
Лучший вариант — не переписывать все docker pull в CI, а научить сам Docker-демон использовать ваш proxy для конкретных реестров.
На хосте, где крутится Docker (dind или host-docker), в /etc/docker/daemon.json можно настроить registry-mirrors:
{
"registry-mirrors": [
"https://registry.example.com"
]
}
После перезапуска Docker все обращения к Docker Hub пойдут через ваш mirror. Это самый прозрачный для CI способ: конфигурация меняется на уровне раннера, скрипты в пайплайнах остаются прежними.
В случае GitLab, если вы используете docker:dind, то нужно создать свой образ dind, в который уже включен daemon.json с registry-mirrors, и использовать его вместо стандартного.
Дополнительно можно включить BuildKit и push образов в тот же реестр, превратив его в центральную точку для всех контейнерных артефактов проекта.
GitHub Actions
В GitHub Actions часто используют actions, работающие с Docker (docker/build-push-action и др.). Там тоже можно управлять тем, через какой реестр идут pulls.
Если вы запускаете self-hosted runner с Docker в режиме host, настройка аналогична: меняете /etc/docker/daemon.json на самом раннере, добавляя mirror, и перезапускаете Docker.
Пример фрагмента workflow, который будет прозрачно использовать mirror (если он настроен на уровне демона):
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Build image
run: |
docker pull node:22-alpine
docker build -t app:latest .
Если же у вас нет доступа к конфигурации Docker-демона (например, GitHub-hosted runners), тогда остаётся либо прямое указание вашего реестра в именах образов, либо использование собственного self-hosted runner в окружении, где вы полностью контролируете Docker.
build cache и влияние registry proxy на скорость сборок
Важно не путать кеш слоёв образов в реестре и build cache Docker/BuildKit, который живёт там, где выполняется сборка.
docker registry proxy cache ускоряет:
- скачивание базовых образов (
FROM ...); - получение слоёв при
docker pullи частично приdocker build, если используются уже существующие слои.
Но основной build cache (кеш слоёв внутри хоста сборки) всё равно находится на CI-раннере. Если runner одноразовый, build cache после каждого джоба теряется, и вам приходится:
- либо использовать remote cache (BuildKit с
--cache-to/--cache-fromв реестре); - либо завязываться на постоянные runnerы с сохранением локального состояния.
registry proxy здесь помогает тем, что:
- ускоряет первичную и повторную загрузку базовых образов для кеша;
- может выступать как хранилище для BuildKit cache (через отдельные теги с
type=registry).
Пример использования BuildKit с registry cache
В расширенном сценарии вы можете хранить build cache слоёв в том же docker registry (включая ваш proxy, если он умеет принимать push от BuildKit). Тогда даже одноразовые runnerы будут реже пересобирать всё с нуля.
Пример запуска docker buildx с кешем в реестре:
docker buildx build --cache-to=type=registry,ref=registry.example.com/org/app:buildcache,mode=max --cache-from=type=registry,ref=registry.example.com/org/app:buildcache -t registry.example.com/org/app:latest .
При таком подходе ваш docker registry VDS становится не просто proxy cache, но и репозиторием build cache. Это дополнительно нагружает диск и сеть, но значительно ускоряет повторные сборки в CI, особенно при сложных Dockerfile.
Если вы уже строите сложную кеширующую инфраструктуру, может быть полезна связанная статья про продвинутый кэшинг статики и изображений в nginx: конвертация и кеширование WebP/AVIF через nginx.

Очистка и политики хранения кеша
Если просто включить proxy cache и ничего с ним не делать, через полгода–год вы обнаружите, что каталог с данными registry раздулся до сотен гигабайт.
Нужно планировать:
- политику retention (какие образы и как долго храним);
- регулярную очистку старых и невостребованных слоёв;
- мониторинг свободного места.
Классический registry:2 не имеет идеального встроенного GC по обращаемости (частоте pull). Есть garbage-collect, но он работает по принципу «удалить все объекты, не привязанные к манифестам» и требует определённой процедуры (остановка или режим readonly).
Типичная стратегия:
- Периодически (раз в N недель) останавливать registry.
- Запускать
registry garbage-collectс конфигом. - Снова запускать registry.
Пример запуска GC (для понимания, как это делается; детали зависят от вашего процесса):
docker stop registry-proxy
registry garbage-collect /etc/docker/registry/config.yml
docker start registry-proxy
Если вам нужна более интеллектуальная политика, обычно рассматривают:
- переход на продвинутые решения (Harbor, GitLab Registry) с более гибкими политиками retention;
- или набор своих скриптов, которые анализируют каталоги, смотрят логи и удаляют редко использующиеся образы.
Для общего понимания подходов к TTL и кеш-ключам может быть полезна статья про использование secure-link и времени жизни кэша в nginx: ограничение доступа и управление сроком жизни кеша в nginx.
Безопасность docker registry proxy на VDS
Несмотря на то что proxy cache чаще всего хранит публичные образы, к безопасности стоит отнестись серьёзно: не хочется превратить VDS в открытую свалку образов и потенциальный вектор атаки.
Базовые практики:
- TLS по умолчанию — не раздавайте registry по HTTP наружу;
- Firewall — закройте порт 5000 снаружи, оставив доступ только к 443 порту nginx;
- Аутентификация — если вы планируете отдавать или принимать приватные образы, настройте basic auth или токены;
- Разделение ролей — CI и разработчикам не обязательно иметь права на push во все репозитории;
- Обновления — регулярно обновляйте образ
registry:2и базовую ОС на VDS.
Отдельно подумайте об изоляции самого Docker-демона на VDS: не стоит запускать на той же машине потенциально недоверенные контейнеры рядом с registry. Лучше пусть эта нода отвечает только за кеш и, возможно, за сопутствующий reverse-proxy.
Типичные проблемы и отладка
При внедрении docker registry proxy cache в CI чаще всего встречаются следующие проблемы.
1. Docker не использует mirror
Симптом: несмотря на настройки, docker продолжает ходить напрямую на Docker Hub.
Проверяем:
- правильно ли прописан
registry-mirrorsвdaemon.json; - перезапущен ли Docker-демон после изменения конфигурации;
- нет ли локального кеша DNS, который резолвит старый адрес;
- смотрим трафик и логи nginx и registry: есть ли вообще запросы с CI.
2. Ошибки авторизации / 401 Unauthorized
Если вы используете proxy не только для публичных, но и для приватных образов, начинаются истории с токенами и basic auth. Классический registry:2 умеет работать с auth backend, но это уже отдельная тема. Для простого proxy к публичному Docker Hub лучше вообще не включать авторизацию.
3. Ошибки push больших образов
Симптом: push валится по таймауту или ошибкам 413 Request Entity Too Large.
Чаще всего виноват nginx:
- не выставлен или мал
client_max_body_size; - слишком маленькие
proxy_read_timeout/proxy_send_timeoutпри медленной сети и больших слоях.
Добавьте или проверьте в конфиге:
client_max_body_size 0;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
4. Неожиданный рост диска
Симптом: раздел с /var/lib/registry быстро заполняется.
Проверьте:
- не собираете ли вы чрезмерно много тегов и не пушите ли их в этот реестр;
- нет ли тестов, которые постоянно создают уникальные теги и никогда не переиспользуют их;
- планируете ли вы регулярный GC и мониторите ли заполнение диска.
Итог: когда docker registry proxy cache на VDS действительно нужен
Подведём итоги, в каких случаях имеет смысл заморачиваться с docker registry proxy cache на отдельном VDS и тащить CI через него:
- у вас много docker-based CI-сборок (десятки–сотни pipelines в день);
- используется один и тот же набор базовых образов, и каждый раз их скачивание заметно бьёт по времени;
- есть проблемы с сетевой доступностью Docker Hub (ограничения, нестабильный канал, rate limits);
- вы хотите централизовать контроль над тем, какие базовые образы реально используются в проектах;
- вам нужен фундамент для дальнейшего развития: приватный реестр, хранение build cache, сложные политики retention.
Если же CI у вас запускается раз в день для пары проектов и образы небольшие, выгода может быть не так заметна. Но как только команда и количество сервисов растут, локальный docker registry proxy cache становится почти обязательной частью DevOps-инфраструктуры.
Главное — относиться к нему как к полноценному сервису: продуманная конфигурация, мониторинг, политики хранения и безопасность. Тогда он действительно ускорит ваши docker build и разгрузит внешние реестры, а не превратится в «чёрную дыру» дискового пространства.


