Собственный private registry решает сразу несколько задач: ускоряет доставку образов внутри вашей инфраструктуры, сокращает трафик в интернет, повышает контроль доступа и даёт предсказуемость поставок. В этой статье разбираем практическую схему на VDS: контейнер registry:2, базовая аутентификация через htpasswd, TLS‑терминация на Nginx, настройка доверия клиентов и безопасная очистка образов (garbage collect), включая автоматизацию.
Архитектура и требования
Базовая схема:
- Docker Registry (образ
registry:2) с файловым хранилищем на диске. - Nginx в роли TLS‑прокси с Basic Auth (через файл
htpasswd), проксирующий на Registry. - Открыт наружу только 443/tcp (и, при необходимости, 80/tcp для редиректов). Сам Registry слушает на 127.0.0.1:5000.
Минимальные вводные:
- Свежая Linux‑система на вашем VDS, установлен Docker и (опционально) Docker Compose.
- Системный фаерволл открыт на 443/tcp для входящих соединений.
- Готовы файлы сертификата и ключа сервера (или временно используем самоподписанный сертификат для тестов).
Практический совет: храните данные реестра на отдельном разделе/диске, включите регулярный бэкап каталога с хранилищем и конфигами.
Подготовка каталогов и пользователей
Создадим структуру каталогов под конфиги, данные и авторизацию:
sudo mkdir -p /srv/registry/{data,auth,conf}
sudo chown -R root:root /srv/registry
sudo chmod 750 /srv/registry
Создаём файл аутентификации htpasswd
Устанавливаем утилиту и создаём первого пользователя:
sudo apt-get update
sudo apt-get install -y apache2-utils
sudo htpasswd -Bbc /srv/registry/auth/htpasswd ci-user strong_password_here
sudo chmod 640 /srv/registry/auth/htpasswd
Позже вы сможете добавлять и менять пользователей:
sudo htpasswd -B /srv/registry/auth/htpasswd another-user
sudo htpasswd -D /srv/registry/auth/htpasswd old-user

Конфиг Docker Registry
Создадим файл /srv/registry/conf/config.yml с осмысленными параметрами: включаем удаление, настраиваем прослушивание на localhost, оставляем файловое хранилище и health‑endpoint.
version: 0.1
log:
level: info
fields:
service: registry
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
http:
addr: 127.0.0.1:5000
headers:
X-Content-Type-Options: [nosniff]
X-Frame-Options: [DENY]
X-XSS-Protection: ["1; mode=block"]
debug:
addr: 127.0.0.1:5001
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
Опция storage.delete.enabled: true нужна для корректного удаления манифестов и последующего GC.
Запускаем Registry контейнер
Вариант 1: напрямую через Docker (systemd поднимет его после перезапуска хоста):
sudo docker run -d --name registry -p 127.0.0.1:5000:5000 -v /srv/registry/conf/config.yml:/etc/docker/registry/config.yml:ro -v /srv/registry/data:/var/lib/registry --restart unless-stopped registry:2
Вариант 2: через docker-compose (файл /srv/registry/docker-compose.yml):
version: "3.9"
services:
registry:
image: registry:2
container_name: registry
restart: unless-stopped
network_mode: host
volumes:
- /srv/registry/conf/config.yml:/etc/docker/registry/config.yml:ro
- /srv/registry/data:/var/lib/registry
Старт:
cd /srv/registry
sudo docker compose up -d
Nginx как TLS‑прокси и Basic Auth
Установим Nginx и настроим сервер на 443 с проксированием к 127.0.0.1:5000. В качестве источника паролей используем ранее созданный htpasswd.
sudo apt-get install -y nginx
Пример конфига Nginx (файл /etc/nginx/sites-available/registry.conf):
map $http_upgrade $connection_upgrade { default upgrade; '' close; }
upstream registry_backend {
server 127.0.0.1:5000;
keepalive 32;
}
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;
# Basic Auth
auth_basic "Registry";
auth_basic_user_file /srv/registry/auth/htpasswd;
# Docker Registry v2 API
location /v2/ {
proxy_pass http://registry_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_read_timeout 900s;
proxy_send_timeout 900s;
}
}
server {
listen 80;
server_name registry.example.com;
return 301 https://$host$request_uri;
}
Активируем сайт и проверяем конфиг:
sudo ln -s /etc/nginx/sites-available/registry.conf /etc/nginx/sites-enabled/registry.conf
sudo nginx -t
sudo systemctl reload nginx

Важно: закройте наружу порт 5000 (Registry) в фаерволле и слушайте его только на 127.0.0.1. Открытым остаётся 443/tcp Nginx.
Сертификаты: самоподписанный или доверенный
Для тестов можно сгенерировать самоподписанный сертификат:
sudo openssl req -x509 -newkey rsa:4096 -nodes -days 365 -keyout /etc/ssl/private/registry.key -out /etc/ssl/certs/registry.crt -subj "/CN=registry.example.com"
sudo systemctl reload nginx
В продакшене используйте доверенный сертификат. Если применяете собственный CA, добавьте его корневой сертификат на клиентские хосты. Получить доверенный TLS проще всего через SSL-сертификаты.
Настройка доверия Docker‑клиентов
Если сертификат не из публично доверенного УЦ, Docker‑клиенты должны доверять вашему CA или конкретному серверному сертификату. На каждом клиенте создайте директорию и положите сертификат:
sudo mkdir -p /etc/docker/certs.d/registry.example.com
sudo cp /path/to/ca.crt /etc/docker/certs.d/registry.example.com/ca.crt
sudo systemctl restart docker
Для узла с нестандартным портом каталог должен называться /etc/docker/certs.d/registry.example.com:443. Не используйте «insecure‑registries», если можете установить доверие к CA — это безопаснее.
Проверка: login, push, pull
# Входим
sudo docker login registry.example.com
# Тегируем локальный образ
sudo docker tag alpine:3.20 registry.example.com/tools/alpine:3.20
# Публикуем
sudo docker push registry.example.com/tools/alpine:3.20
# Проверяем скачивание
sudo docker pull registry.example.com/tools/alpine:3.20
Если видите x509: certificate signed by unknown authority — клиент не доверяет вашему сертификату/CA. Проверьте, что файл ca.crt корректно установлен и перезапущен Docker.
Политика прав и учётные записи
Basic Auth — простой и надёжный вариант для единого периметра. Рекомендации:
- Используйте
htpasswd -Bс достаточной стоимостью (-C 12и выше). - Разделяйте учётки по командам/CI‑системам и периодически ротируйте пароли.
- Ограничивайте доступ к реестру на уровне сети (firewall, VPN, allow‑list по IP).
Очистка образов: delete + garbage collect
Registry хранит слои, связанные с манифестами. Чтобы освободить место, сначала удаляем манифесты, затем запускаем сборщик мусора (GC). Мы уже включили storage.delete.enabled: true, значит API позволит удаление.
Удаление манифеста по digest
Алгоритм:
- Получить digest манифеста по тэгу.
- Удалить манифест по digest.
Пример с curl (понадобится Basic Auth):
# 1) Узнаём digest по тэгу (важен Accept для манифестов v2)
curl -I -u ci-user:strong_password_here -H "Accept: application/vnd.docker.distribution.manifest.v2+json" https://registry.example.com/v2/tools/alpine/manifests/3.20
# В ответе ищем заголовок Docker-Content-Digest: sha256:...
# 2) Удаляем манифест по digest
curl -X DELETE -u ci-user:strong_password_here https://registry.example.com/v2/tools/alpine/manifests/sha256:...
Удаление всех ненужных тэгов переводит «висячие» слои в кандидаты на сборку мусора.
Запуск garbage collect
Классическая рекомендация — останавливать трафик на время GC. Самый простой способ: временно закрыть доступ в Nginx или остановить контейнер Registry, выполнить GC в одноразовом контейнере и снова запустить сервис.
# Останавливаем Registry
sudo docker stop registry
# Запускаем GC в одноразовом контейнере, монтируя те же тома
sudo docker run --rm -v /srv/registry/conf/config.yml:/etc/docker/registry/config.yml:ro -v /srv/registry/data:/var/lib/registry registry:2 registry garbage-collect /etc/docker/registry/config.yml
# Запускаем Registry обратно
sudo docker start registry
Если вам важно свести простой к минимуму, можно гасить трафик на уровне Nginx (например, временным deny all на /v2/) и выполнять GC с минимальным окном.
Автоматизация GC через systemd timer
Создадим сервис /etc/systemd/system/registry-gc.service:
[Unit]
Description=Docker Registry garbage-collect
Wants=docker.service
After=docker.service
[Service]
Type=oneshot
ExecStart=/usr/bin/docker stop registry
ExecStart=/usr/bin/docker run --rm -v /srv/registry/conf/config.yml:/etc/docker/registry/config.yml:ro -v /srv/registry/data:/var/lib/registry registry:2 registry garbage-collect /etc/docker/registry/config.yml
ExecStart=/usr/bin/docker start registry
[Install]
WantedBy=multi-user.target
Таймер /etc/systemd/system/registry-gc.timer (например, еженедельно ночью):
[Unit]
Description=Weekly Docker Registry GC
[Timer] *-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target
Активируем таймер:
sudo systemctl daemon-reload
sudo systemctl enable --now registry-gc.timer
Мониторинг и наблюдаемость
- Проверка каталога:
GET /v2/_catalog(за Basic Auth и TLS). - Health‑endpoint:
http://127.0.0.1:5001/debug/healthвнутри сервера. - Логи Nginx и Registry: анализируйте коды 401/403/5xx, пиковые времена и размеры загрузок.
Безопасность
- Держите Registry на
127.0.0.1, наружу — только Nginx на 443. - Минимизируйте набор пользователей, включайте ротацию паролей и ограничение по IP на уровне фаерволла.
- В текстах и CI не храните пароли в открытом виде, используйте секреты среды выполнения.
- Следите за параметром
client_max_body_sizeв Nginx:0отключает лимит, но это уместно лишь в доверенной сети.
Если реестр крутится на общем хосте с другими рабочими нагрузками, подумайте об изоляции. Полезно начать с обзора про изоляцию контейнеров (gVisor/Firecracker).
Производительность и стабильность
- Включите
keepaliveк backend иhttp2на фронте — это улучшит конвейеризацию и скорость слоёв. - Увеличьте
proxy_read_timeoutиproxy_send_timeoutпри больших образах и медленных дисках. - Дисковая подсистема важнее всего: используйте быстрые SSD, следите за inode и свободным местом.
Бэкапы и восстановление
Для файлового хранилища достаточно копии каталога /srv/registry/data и конфигов. При восстановлении убедитесь, что совпадают доменное имя и пути — клиенты ожидают прежний CN в сертификате. Перед восстановлением остановите Registry, а после — выполните проверочный docker pull нескольких критичных образов.
Типичные ошибки и их решение
- 401 Unauthorized: проверьте логин/пароль, права на файл
htpasswd, конфиг Nginx сauth_basic. - 413 Request Entity Too Large: увеличьте
client_max_body_sizeна 443‑сервере. - x509 unknown authority: установите правильный
ca.crtдля реестра на клиентах и перезапустите Docker. - timeout при push/pull: увеличьте таймауты прокси и проверьте I/O диска на VDS.
Итоги
Мы подняли приватный Docker Registry на VDS с базовой аутентификацией, TLS на Nginx и настроили безопасную уборку пространства: удаление манифестов по API и периодический garbage collect. При такой схеме вы контролируете доступ, экономите трафик и снижаете риски сбоев внешних реестров. Дальше можно добавить IP‑allowlist, отдельные учётки для CI/CD и метрики в систему мониторинга.


