
Нужен был реестр артефактов. Показать студентам цепочку поставки софта: сборка, тесты, push в реестр, деплой. Стандартная задача, казалось бы. "Вошли и вышли, приключение на 20 минут."
Растянулось на несколько месяцев.
В итоге написал свой реестр. Один бинарник. 7 форматов. 12 МБ RAM. Без базы данных.
Кладбище альтернатив
Nexus. Администрировал его годами на разных площадках. Знаю хорошо, где blob store хранит метаданные, как не убить OrientDB после апгрейда, а если окирпичилась – как откачать, куда смотреть, когда cleanup policy молча сожрала нужный артефакт.
С версии 3.71 Sonatype выкинули OrientDB и переделали линейку: Core (OSS, EPL, H2 embedded), Community (бесплатная, PostgreSQL, проприетарная лицензия, лимит 200K запросов/день), Pro (HA, replication, SSO). Миграция между версиями – это то ещё отдельное извраприключение.
4 гига RAM на Java-процесс. Образ 600 МБ.
Artifactory. OSS-версия формально существует (Apache 2.0), но поддерживает только Maven/Gradle/Ivy. Docker, npm, PyPI, Helm – всё платное. А платное начинается со слов «свяжитесь с отделом продаж». «Хьюстон, у нас проблема!» – «Окей, мы вас вычёркиваем.»
Harbor. Контейнерный реестр – Docker-образы, Helm charts, OCI-артефакты. npm, Maven, PyPI не завезли. PostgreSQL + Redis + 2 ГБ минимум и вот это всё.
GitLab Container Registry. Попробовал – завелось. А потом начался цирк. OCI artifacts и cosign формально работают, но через обходные решения. Buildx cache? Ставь provenance=false, иначе ломается. Мультиарх? С оговорками. Каждый нестандартный сценарий опять отдельный квест. Если бы мне платили за каждый квест...
В какой-то момент посчитал: я трачу больше времени на костыли, чем потратил бы на свой реестр.
Как появилась NORA
У меня дома жил бурундук. Маленький, шустрый, тащит всё к себе в нору. Когда понадобилось название для реестра – ну, вы поняли.
Задача на старте была скромная: Docker Registry v2, один бинарник, без внешних сервисов. Чтобы человек сделал docker run, получил рабочий реестр и через 3 секунды пушил образы.
Первая версия заработала. Push, pull, delete, каталог, теги – спецификация покрыта. А дальше случилось то, что случается с каждым «скромным» pet-проектом.
NORA написана на Rust, а крейты нужно где-то хранить. Поднимать отдельный Cargo registry ради этого не хотелось – проще дописать поддержку прямо в NORA. Sparse index по RFC 2789, config.json, cargo publish – готово.
Два формата есть. npm, Maven, PyPI? Аппетит приходит во время еды. У каждого формата свои сюрпризы. Cargo: RFC говорит «отдавай JSON по GET». На деле – 4 паттерна URL и молчаливый «crate not found». Сидишь, разговариваешь с tcpdump о вечном. Но каждый следующий формат добавлялся быстрее – архитектура настоялась.
Сейчас поддерживается 7 типов:
Реестр | Что хранит | Путь |
Docker v2 + OCI | Образы, Helm charts |
|
Maven | JAR, WAR, POM |
|
npm | Node.js-пакеты |
|
PyPI | Python-пакеты |
|
Cargo | Rust-крейты |
|
Go | Go-модули |
|
Raw | Что угодно |
|
Без базы данных
Нет внешней БД. Ни PostgreSQL, ни Redis, ни Elasticsearch – ничего рядом не крутится. Метаданные лежат на файловой системе рядом с артефактами.
Небольшая команда, один CI/CD, сотни артефактов – скрипач не нужен. Файловая система на SSD справляется. Каталог образов собирается обходом директории, метаданные читаются как JSON с диска.
Не надо поднимать Postgres, создавать базу, накатывать миграции.
Консистентность обеспечивается атомарным переименованием. S3-бэкенд работает. БД и HA , когда дойдёт до enterprise. Пока отлаживаем фундамент.
nora (34 МБ, Rust, 450+ тестов) ├── /v2/ — Docker Registry v2 + OCI (образы, Helm charts) ├── /maven/ — Maven (JAR, WAR, POM) ├── /npm/ — npm (Node.js-пакеты) ├── /pypi/ — PyPI (Python-пакеты) ├── /cargo/ — Cargo (Rust-крейты, sparse index RFC 2789) ├── /go/ — Go Modules ├── /raw/ — Raw (что угодно) ├── /metrics — Prometheus ├── /health — K8s liveness probe ├── /ready — K8s readiness probe ├── /api-docs — Swagger └── /data/ — хранилище (FS или S3)
Ноль управления через веб
Кто-то зашёл в админку Nexus, поправил настройки проксирующего репозитория, переключил layout, добавил routing rule – и ни одна живая душа не знает, что изменилось. По логам восстановить, кто вчера сломал docker-group, можно, но, честно говоря, сомнительное удовольствие.
В NORA нет админки для изменения конфигурации. Совсем. Вся настройка – переменные окружения или config.toml. Нужен новый upstream для Docker? Поменял NORA_DOCKER_UPSTREAMS, перезапустил. Изменение попадает в git, в историю деплоя, в audit trail CI/CD.
Web UI есть, но read-only: просмотр артефактов, поиск, статистика. Как говорится, рыбов показываем, но не продаём – конфигурация только через файлы и CLI.
Безопасность
Аутентификация поддерживает два механизма:
htpasswd – классика из мира Apache/nginx. Файл с хешами, работает из коробки.
API-токены с RBAC. Три роли: read, write, admin. Токены отзываемые. Утёк ключ CI-раннера – отозвал за секунду. В Nexus есть локальные роли, но для нормального RBAC всё равно тянут LDAP.
Режим anonymous read: pull без авторизации, push с токеном.
TLS вешаю на reverse proxy (nginx, Caddy, Traefik) – стандартная схема. NORA слушает чистый HTTP.
Что ещё внутри
Логи. Структурированный JSON: метод, путь, статус, время, пользователь. Льёшь в Loki, OpenSearch, ClickHouse – видишь, кто, что, когда пушил. В Nexus для этого придётся ковырять access log + audit log + парсинг на коленке.
Метрики и ops. /metrics — Prometheus из коробки. /health, /ready — K8s probes. /api-docs — Swagger. Один scrape_config в Prometheus и дашборд готов.
Rate limiting. По эндпоинтам, через переменные окружения. 50 pipeline'ов одновременно дёргают docker pull и никто не страдает.
Audit log. Лог действий в формате JSONL: кто, что, когда. Пишется на диск, изменить задним числом нельзя.
GC. nora gc --dry-run – покажет осиротевшие Docker-блобы. Без --dry-run подчистит. Для остальных форматов пока в roadmap.
Бэкап. nora backup -o backup.tar.gz – один файл, полная копия. nora restore для восстановления. Ну а бэкап Nexus – это blob store + БД + надежда, что всё сойдётся.
"Отечественные"-сборки. Docker-образы на базе Astra Linux SE и RED OS в каждом релизе. Для площадок, где без бумажки ты никто.
Цифры
NORA | Nexus OSS | Artifactory | |
Docker-образ | 34 МБ | 600+ МБ | 1+ ГБ |
Холодный старт | ~3 сек | 30-60 сек | 30-60 сек |
RAM | 12 МБ | 2-4 ГБ | 2-4 ГБ |
Язык | Rust | Java | Java |
Внешние сервисы | 0 | H2 (Core) / PostgreSQL (Community/Pro) | PostgreSQL + Tomcat |
Лицензия | MIT | EPL-1.0 (Core) / Proprietary (Community, Pro) | Apache 2.0 (OSS, только Maven) / Proprietary |
Форматов | 7 | 20+ | 30+ |
Под нагрузкой
VM с двумя ядрами, 4 ГБ RAM, Proxmox. docker pull образа 268 МБ за 6 секунд, docker push за 19 секунд. Push и pull одновременно не мешают друг другу. 0 ошибок, 0 таймаутов. RAM под нагрузкой в районе 250 МБ (idle 12 МБ).
Вектор развития
v0.5 (сейчас) | v1.0 (ближе к лету) |
GC вручную ( | Online GC, политики хранения |
Single-node | Multi-node с distributed locking |
7 форматов | + NuGet, RubyGems, Conan |
htpasswd + API-токены | Управление токенами через веб |
Пока нет replication, LDAP/OIDC, NuGet и RubyGems.
Поднять
docker run -d \ -p 8080:8080 \ -v nora-data:/data \ getnora/nora:0.5.0
Всё. Реестр на порту 8080. Веб-интерфейс, API, все 7 реестров.
Docker login + push
docker login localhost:8080 -u admin docker tag myapp:latest localhost:8080/myapp:latest docker push localhost:8080/myapp:latest
npm publish
npm config set registry http://localhost:8080/npm/ npm publish
pip install
pip install --index-url http://localhost:8080/pypi/simple/ mypackage
mvn deploy
mvn deploy -DaltDeploymentRepository=nora::default::http://localhost:8080/maven/
Прокси к upstream
Не только Docker Hub. NORA проксирует запросы ко всем основным источникам: Docker Hub, npmjs.org, Maven Central, pypi.org, proxy.golang.org. Образа или пакета нет локально – NORA сходит в upstream, скачает, закеширует, отдаст. Следующий запрос уже из кеша.
docker pull localhost:8080/nginx:latest # → Docker Hub npm install express --registry http://localhost:8080/npm/ # → npmjs.org
Для приватных upstream предусмотрен Basic Auth через конфиг.
Закрытый контур
Типовая схема: внешний контур (DMZ) с раннером, который тянет артефакты из публичных реестров, проверяет, например, по методике ФСТЭК (целостность, подпись, сканирование и др.), и перекладывает во внутренний контур. Стандартный подход – bash-скрипт на 200 строк, у каждой команды свой велосипед.
В NORA это встроено. Две инсталляции – внешняя и внутренняя – и команда переноса:
# Внешний контур: зеркалирование из upstream nora mirror --format docker --packages "nginx:latest,redis:7" --output ./bundle # Раннер переносит bundle во внутренний контур # Внутренний контур: восстановление nora restore --input ./bundle
Зеркалирование работает для Docker, npm, PyPI, Maven, Cargo. Целостность при restore проверяется по content digest (sha256).
Где мы сейчас
- Changelog | Compatibility matrix
- В Awesome Docker
- Работает в продакшене: CI/CD сборки моих проектов, учебные кластеры на Proxmox – docker push/pull, npm, maven ежедневно
- NORA хранит собственные Docker-образы и Rust-крейт nora-registry, CI тянет зависимости через NORA как proxy к crates.io

Демо: demo.getnora.io
Исходники: github.com/getnora-io/nora
Баги и пожелания – в Issues.
