В одной из предыдущих статей, а именно в «Применение Portainer в CI/CD процессах», мы разобрались, что такое сборка Docker-образов и какие существуют варианты их хранения. В том примере использовался GitHub Docker Registry, а в своей работе я применяю хранилище в собственном Git-хостинге на базе Gitea.

Альтернативой хранению образов рядом с кодом является самостоятельный (self-hosted) репозиторий образов, например Harbor.

В этой статье мы разберём, что такое Harbor, как установить его на свой сервер и как начать им пользоваться.


Что такое Harbor?

Harbor — это open-source решение для хранения Docker-образов на собственном сервере (self-hosted). Оно активно используется как в небольших проектах, так и в корпоративном сегменте. Дополнительно Harbor интегрируется с Trivy, который выполняет сканирование образов на наличие уязвимостей.

Ключевые особенности

  • UI и API для управления репозиториями образов.

  • RBAC — разграничение прав на уровне проектов, пользователей и групп.

  • Аутентификация и SSO (LDAP, OIDC, Keycloak и т.д.).

  • Поддержка различных типов артефактов — не только контейнеров, но и Helm-чартов, OCI-артефактов и других.

  • Встроенный прокси-кэш для зеркалирования внешних регистри.

  • Встроенный сканер уязвимостей (Trivy).

  • Репликация образов между несколькими инстансами Harbor.

  • Подпись и проверка образов через Notary.

  • Аудит-лог действий пользователей.

  • Многоуровневая политика хранения (retention policies).

Системные требования

Минимально

Рекомендовано

ЦПУ

2 CPU

4 CPU

ОЗУ

4 GB

8 GB

Диск

40 GB

160 GB

Что такое Trivy

Trivy — это open-source сканер уязвимостей и ошибок конфигурации (misconfig) от Aqua Security. Он умеет проверять:

  • Образы контейнеров — как ОС-пакеты, так и зависимости приложений (npm, pip, Go, Java, Ruby и т.д.).

  • Файловые системы и репозитории — ищет уязвимые зависимости прямо в коде проекта.

  • Kubernetes и IaC-манифесты — Helm, Kubernetes, Terraform и другие, на предмет небезопасных настроек.

  • SBOM — читает и генерирует SPDX/CycloneDX, а также сканирует зависимости по готовому SBOM-файлу.

  • Секреты — базовый поиск «утёкших» ключей и токенов в коде.

Как Trivy интегрирован в Harbor

Trivy в Harbor интегрирован как отдельный сервис-адаптер, который:

  • Сканирует образы вручную по запросу или автоматически при push (если включена эта функция).

  • Сохраняет отчёт по CVE с классификацией уязвимостей по уровням LOW / MEDIUM / HIGH / CRITICAL.

  • Позволяет задать политику блокировки pull (например, запрет на загрузку образов с уязвимостями уровня High и выше).

  • Поддерживает allowlist (игнор-лист) на уровне проекта.


Деплой Harbor

Приступим к установке Harbor на собственный сервер.

Что понадобится:

  • Домен второго или третьего уровня.

  • VPS, соответствующий системным требованиям.

  • Reverse-proxy — в моём случае это будет Caddy.

Конфигурация Harbor

Подключаемся к серверу по SSH, создаём директорию и переходим в неё:

mkdir harbor && cd harbor

Скачиваем установщик. Возьмём последнюю актуальную версию — 2.13.2:

curl -LO https://github.com/goharbor/harbor/releases/download/v2.13.2/harbor-online-installer-v2.13.2.tgz

Распаковываем архив и переходим в директорию:

tar xzf harbor-online-installer-v2.13.2.tgz && cd harbor

Переименовываем пример конфигурационного файла и открываем его для редактирования:

cp harbor.yml.tmpl harbor.yml && nano harbor.yml

Изменяем следующие параметры:

  • hostname — указываем домен без https://.

  • https — закомментируем весь блок, так как SSL-сертификат и обработку HTTPS в нашем случае будет обеспечивать Caddy.

  • external_url — раскомментируем параметр и укажем домен с префиксом https, например: https://<ваш_домен>

  • harbor_admin_password — зададим сложный пароль администратора Harbor.

  • database.password — укажем пароль для базы данных.

Остальные параметры можно оставить по умолчанию или адаптировать под свои нужды.

Сохраняем изменения (CTRL+S) и выходим из редактора (CTRL+X).

Запускаем генерацию конфигурационных файлов с включённым Trivy:

sudo ./prepare --with-trivy

На этом этапе будут загружены и подготовлены необходимые конфигурационные файлы.

Правки docker-compose.yml

Так как у меня Caddy работает в отдельном Docker Compose, Harbor нужно подключить к той же сети, что и Caddy.

Открываем файл для редактирования:

nano docker-compose.yml

В самом конце, в блоке networks, добавляем внешнюю сеть:

networks:
  harbor:
    external: false
  caddy_net:
    name: caddy_net
    external: true

Далее находим сервис proxy и вносим изменения:

  1. Удаляем блок ports, чтобы прокси не слушал порт напрямую и был доступен только через Caddy.

  2. Меняем значение container_name на harbor-proxy — так будет проще ориентироваться.

  3. В блок networks добавляем внешнюю сеть — в моём случае это caddy_net.

Настройка Caddy

Открываем Caddyfile и добавляем проксирование на nginx Harbor:

<ваш_домен> {
    encode {
        gzip
    }

    header {
        X-Content-Type-Options "nosniff"
    }

    reverse_proxy harbor-proxy:8080
}

Так Caddy будет обрабатывать входящие HTTPS-запросы и передавать их на Harbor.

Запуск

Для запуска выполняем команду:

docker compose up -d

Дожидаемся, пока скачаются образы и запустятся все контейнеры.

Когда Harbor будет запущен, открываем в браузере адрес вашего домена.

Если всё прошло успешно, вас встретит страница с формой входа — значит, установка завершена и сервис готов к работе.


Проект и отправка образа

Деплой прошёл успешно, теперь создадим проект и загрузим в него Docker-образ.

Что такое проект в Harbor?

В Harbor проект — это отдельное пространство для хранения артефактов (Docker-образов, Helm-чартов и др.), с собственными настройками доступа, политиками хранения и сканирования.

Проекты позволяют:

  • Разделять образы по приложениям, командам или окружениям (dev, staging, prod).

  • Настраивать права доступа для отдельных пользователей или групп.

  • Включать или отключать автоматическое сканирование уязвимостей.

  • Управлять retention-политикой (удаление старых или неиспользуемых образов).

Это удобно, когда в одном Harbor работают сразу несколько команд или сервисов.

Создание проекта

В левом меню переходим в раздел "Projects" — откроется страница со списком проектов.

По умолчанию уже есть проект library, но мы создадим свой. Нажимаем кнопку «New project».

В появившемся окне заполняем поля:

  • Название проекта — обязательно в нижнем регистре.

  • Уровень доступа — публичный или приватный.

  • Лимит дискового пространства — в гигабайтах или -1 для отключения лимита.

  • Переключатель Proxy Cache — при включении, если образа нет в вашем реестре, Harbor попытается получить его из другого источника и сохранить локально. Это удобно для часто используемых образов, но перед этим в разделе «Registries» нужно добавить источники.

После заполнения нажимаем «Ok» — проект появится в списке.

Переходим в созданный проект — откроется страница реестра с набором функций.

Чтобы получить подсказки по отправке образов, нажмите «PUSH COMMAND».

Здесь же указан адрес проекта в формате:

<ваш_домен>/<ваш_проект>/<название_образа>

Отправка образа в репозиторий

Для примера я воспользуюсь CI/CD-конфигурацией из статьи «Применение Portainer в CI/CD процессах».

Изначально у нас было так:

- name: Log in to GitHub Container Registry  
  uses: docker/login-action@v3  
  with:  
    registry: ghcr.io  
    username: ${{ github.actor }}  
    password: ${{ secrets.GITHUB_TOKEN }}  

- name: Build and push Docker image  
  uses: docker/build-push-action@v6  
  with:  
    context: .  
    push: true  
    cache-from: type=registry,ref=ghcr.io/prodreams/tempproject:latest  
    cache-to: type=inline  
    tags: |  
      ghcr.io/prodreams/tempproject:latest    
      ghcr.io/prodreams/tempproject:${{ github.sha }}

Чтобы отправлять образы в Harbor, нужно заменить адреса и реквизиты на новые:

- name: Log in to Harbor Registry  
  uses: docker/login-action@v3  
  with:  
    registry: <ваш_домен>/<ваш_проект>  
    username: ${{ secrets.HARBOR_USER }}  
    password: ${{ secrets.HARBOR_PASSWORD }}  

- name: Build and push Docker image  
  uses: docker/build-push-action@v6  
  with:  
    context: .  
    push: true  
    cache-from: type=registry,ref=<ваш_домен>/<ваш_проект>/tempproject:latest  
    cache-to: type=inline  
    tags: |  
      <ваш_домен>/<ваш_проект>/tempproject:latest    
      <ваш_домен>/<ваш_проект>/tempproject:${{ github.sha }}

Полный пример доступен в репозитории на GitHub: ссылка

После внесения изменений отправляем коммит в репозиторий и ждём завершения сборки образа:

Проверяем, что образ появился в репозитории Harbor:

Перейдя внутрь образа, можно запустить проверку на уязвимости (или включить автоматическую проверку в настройках проекта). В отчёте отобразятся все найденные уязвимости с указанием их уровня опасности:


Заключение

Harbor — это мощное и удобное решение для организации собственного Docker Registry, которое подойдёт как для небольших команд, так и для крупных компаний. Я лично планирую перенести в него все свои образы и настроить гибкое распределение доступов.

Мир open-source решений постоянно развивается, и впереди ещё множество интересных инструментов, которые можно опробовать и внедрить в свои продакшн-процессы.

А если вы хотите узнавать о таких инструментах и лучших практиках их применения — присоединяйтесь к нашему Telegram-каналу «Код на салфетке»!

Комментарии (6)


  1. Arduinum
    14.08.2025 12:13

    Очень удобное хранение контейнеров.


    1. proDream Автор
      14.08.2025 12:13

      Больше всего интересно разграничение доступов на те или иные образы по конкретным пользователям)


  1. kozlyuk
    14.08.2025 12:13

    По опыту использования Harbor как внутри компании, так и для публикации образов вовне.

    Что хорошо:

    • Из коробки целостное решение, которое можно отдать пользователям, и они накликают, что им нужно.

    • Очистка неиспользуемых образов, политики очистки. Must have для хранилища образов с CI.

    • На уровне конфигов сделано расширяемо и ремонтопригодно.

    Что плохо:

    • Убогий UI за пределами базовых задач. В таблицах ни сортировки, ни гибкого поиска, ни достаточной ширины столбцов (вы же знаете, что там будет SHA256!). Никакую аналитику: что сколько занимает, что качают, кто и откуда — толком не сделать, хотя отдельные данные видны.

    • Контроль доступа только через предопределенные роли в проектах. Например, нельзя ограничить версии образа, доступны конкретному пользователю, кроме как скопировать их в отдельный проект.

    • Нет групп пользователей, нет массовых действий.

    • До сих пор не проработан вариант раздельного доступа из internet и intranet.

    Многие недостатки обходятся через кастомные конфиги или API при желании, но это уже не "из коробки все есть". Субъективно не нравится, что это комбайн, для частей которого всегда есть лучшие альтернативы.


    1. proDream Автор
      14.08.2025 12:13

      Отличное дополнение к статье! Для моих целей думаю это будет лучше хранения образов в Gitea, но если кто-то серьёзнее будет выбирать и наткнётся на статью и ваш комментарий, это может помочь. Спасибо =)


  1. NME_1337
    14.08.2025 12:13

    А чем хуже тот же sonatype nexus?


    1. proDream Автор
      14.08.2025 12:13

      Не пробовал его, если есть опыт, поделитесь =)