Как мы уже знаем любой контейнер при создании использует какой-то образ. И образы эти где-то нужно хранить, по умолчанию образы хранятся в DockerHub. Но рано или поздно вы всё равно столкнётесь с тем что придётся использовать локальный репозиторий образов Docker, т.е. репозиторий размещённый только в вашей локальной сети. На то много причин начиная с банальной безопасности заканчивая простой экономией трафика и скоростью создания контейнеров.

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

  • Всегда используйте ssl для прода. По умолчанию Docker всегда будет пытаться скачать образы с репозитория используя протокол https. Но эта статья носит характер лишь ознакомления с локальным репозиторием, поэтому здесь я буду использовать http.
  • Удаление старых неиспользуемых образов. Если у вас бывают ситуации что в день происходит сборка или скачивание образов не один раз, то через какое-то время образы могут занимать очень много дискового пространства. Поэтому если эти образы более не используются рекомендуется их удалять.

Вариант 1️⃣: Используем образ registry

Суть идеи в том, что создаётся контейнер, внутри которого репозиторий. Далее вы выкачиваете (pull) образ с DockerHub или любого другого репозитория себе на локальное устройство, можно даже импортировать образ из архива локально. Главное, чтобы локально у вас был тот самый образ, который вы хотите разместить в своём репозитории. После того как образ скачан можно его отправить (push) в локальный репозиторий.

1️⃣ Разворачиваем сам контейнер

Для создания локального репозитория будем использовать образ registry. Но так как это пример я не буду сейчас тонко настраивать этот репозиторий. Я лишь покажу что можно использовать для хранения образов локально и поэтому сейчас не буду настраивать https. Если вдруг это вам необходимо напишите об этом в комментарии.

mkdir /opt/docker-registry
docker run -d -p 5000:5000 -v /opt/docker-registry:/var/lib/registry --name my-registry registry:2
docker ps
b5d2f73cb0aa   registry   "/entrypoint.sh /etc…"   39 seconds ago   Up 38 seconds   0.0.0.0:5000->5000/tcp, [::]:5000->5000/tcp   my-registry

2️⃣Скачиваем образ

Для примера я загружу образ nginx:1.29.4, и после загрузки проверю что локально на моём устройстве он присутствует.

docker pull nginx:1.29.4
docker image ls nginx
nginx:1.29.4 4af177a024eb        161MB             0B

3️⃣Тэгируем скачанный образ

Добавляем новый тэг для загруженного образа. Дело в том, что если вы используете любой другой репозиторий, не DockerHub то и наименование вашего образа должно содержать dns имя этого репозитория, ну или ip-адресс если нет dns имени. Для примера у того же Microsoft есть свой публичный репозиторий и если мы хотим скачать что-то оттуда то имя образа уже будет содержать не только привычное нам имя приложения (nginx) а еще и полный путь образа (mcr.microsoft.com/dotnet/framework/runtime), т.е. при тэгировании образа для репозитория вы должны придерживаться следующему наименованию [HOST[:PORT]/][NAMESPACE/]REPOSITORY[:TAG].

docker tag nginx:1.29.4 172.31.144.9:5000/nginx:1.29.4
docker image ls | grep nginx
172.31.144.9:5000/nginx:1.29.4   4af177a024eb        161MB             0B

Во многих примерах вы можете встретить записи localhost/127.0.0.1 но я рекомендую прописывать сразу локальный ip-адресс хоста, тем более если репозиторий вы развернул на другом устройстве в сети, а push/pull делаете с другого.

4️⃣Загрузка образа в локальный репозиторий

Теперь самое простое, так как по умолчанию нет никакой авторизации для загрузки образа в репозиторий созданный из образа registry мы просто прописываем push.

docker push 172.31.144.9:5000/nginx:1.29.4
Get "https://172.31.144.9:5000/v2/": http: server gave HTTP response to HTTPS client

Но при попытке сделать push я получаю ошибку, потому что по умолчанию Docker пытается использовать https. Для того чтобы он не пытался это делать при обращении к 172.31.144.9, нужно прописать этот ip-адресс в файле /etc/docker/daemon.json.

⚠️ Внимание: Перезапуск Docker daemon остановит все запущенные контейнеры, включая my-registry. После перезапуска нужно запустить контейнер заново.

sudo vim /etc/docker/daemon.json
{
  "insecure-registries": ["172.31.144.9:5000"]
}
sudo systemctl restart docker
docker info
docker start my-registry
docker push 172.31.144.9:5000/nginx:1.29.4
1.29.4: digest: sha256:a6dd519f4cc2f69a8f049f35b56aec2e30b7ddfedee12976c9e289c07b421804 size: 1778

Так как я использовал volume при создании контейнера, то и все файлы загружаемые в репозиторий теперь должны быть в локальной директории хоста.

sudo tree /opt/docker-registry/docker/registry/v2/repositories/nginx/_manifests
/opt/docker-registry/docker/registry/v2/repositories/
    ├── _manifests
       ├── revisions
          └── sha256
              └── a6dd519f4cc2f69a8f049f35b56aec2e30b7ddfedee12976c9e289c07b421804
                  └── link
       └── tags
           └── 1.29.4
               ├── current
                  └── link
               └── index
                   └── sha256
                       └── a6dd519f4cc2f69a8f049f35b56aec2e30b7ddfedee12976c9e289c07b421804
                           └── link

5️⃣Скачиваем образ уже с локального репозитория

Теперь, когда я захочу загрузить образ не с репозитория DockerHub а с локального репозитория я также выполняя команду docker pull указываю полный путь образа. Но опять же не забываем добавить исключение в daemon.json.

docker pull 172.31.144.9:5000/nginx:1.29.4

docker-registry-setup.svg

6️⃣Создаём кэширующий репозиторий

И теперь представим ситуацию что вдень нужно загрузить около 5-10 образов и каждый раз так выкачивать образы с DockerHub в локальный репозиторий делать вручную проблематично. Поэтому проще сделать кэширующий registry.

sudo vim /opt/docker-registry/config.yml
version: 0.1
log:
  level: info
storage:
  filesystem:
    rootdirectory: /var/lib/registry
http:
  addr: :5000
proxy:
    remoteurl: https://registry-1.docker.io
docker rm -f my-registry
docker run -d \
--name registry-mirror \
-p 5000:5000 \
-v /opt/docker-registry/config.yml:/etc/docker/registry/config.yml \
-v /opt/docker-registry:/var/lib/registry \
registry:2

Теперь можно делать pull напрямую через registry, он сам перенаправит запрос на https://registry-1.docker.io и выкачает образ себе, после чего отдаст нам. И если при другой компьютер в сети сделает тот же самый pull то registry сперва проверит есть ли какие-то обновления в образе на самом DockerHub и если их нет он отдаст образ, который есть у него локально. Т.е. не будет постоянно заново выкачивать образы при каждом обращении.

docker pull 172.31.144.9:5000/library/nginx:1.28.1-alpine-otel
sudo find /opt/docker-registry/ -name 1.28.1-alpine-otel
/opt/docker-registry/docker/registry/v2/repositories/library/nginx/_manifests/tags/1.28.1-alpine-otel

Также чтобы постоянно не писать полный путь до репозитория (172.31.144.9:5000/library/) при выполнении команды docker pull можно прописать зеркало в файле /etc/docker/daemon.json.

sudo vim /etc/docker/daemon.json
{
  "registry-mirrors": ["http://172.31.144.9:5000"]
}
sudo systemctl restart docker
docker pull library/nginx:1.28-alpine3.23

docker-registry-caching.svg

Вариант 2️⃣: Используем nexus sonatype

Я рекомендую использовать именно этот вариант, только держите в уме что здесь я просто обозреваю продукт и настраиваю именно для демонстрации, а не продакшена. Опять же если будет интересно подробная настройка пишите в комментариях.

Для начала создаём контейнер из образа sonatype/nexus3. После того как контейнер поднимется можно переходить в браузере по ссылке http://172.31.144.9:8081/.

docker run -d -p 8081:8081 -v nexus-data:/nexus-data --name nexus sonatype/nexus3 

При первом входе необходимо вбить автоматически сгенерированный пароль и после поменять его на свой. имя пользователя admin.

docker exec -it nexus bash
cat /nexus-data/admin.password
25689260-a833-4193-adf5-1b210be3ab30

Далее переходим к настройке, наша основная цель - это создание прокси репозитория для DockerHub и включение возможности анонимного docker pull.

  1. Переходим по ссылке и создаём прокси репозиторий для Docker. enter image description here enter image description here
  2. Не забываем включить Enable anonymous access.
  3. Дальше идём http://172.31.144.9:8081/#admin/security/realms и делаем так. enter image description here

Теперь всё готово для использования только что созданного локального репозитория и магия тут в том, что не нужно ничего выкачивать с DockerHub я напрямую обращусь к локальному репозиторию дальше сам Nexus пойдёт на DockerHub и скачает образ, разместит его у себя и уже после отдаст мне образ с локального репозитория. Т.е. вместо 3 запросов как в примере ранее тут достаточно выполнить один запрос. При этом если образ на DockerHub не менялся, то и при последующем запросе сам Nexus не будет его скачивать заново.

docker pull 172.31.144.9:8081/dockerhub-proxy/nginx:1.28.1-alpine

Если же вам нужно загружать свои образы в репозиторий то необходимо создай другой docker репозиторий (hosted).

nexus-sonatype-setup.svg

Сравнительная таблица: Registry vs Nexus

docker-registry-vs-nexus