
Нужен был реестр артефактов. Показать студентам цепочку поставки софта: сборка, тесты, 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.
Комментарии (24)

atatarn
11.04.2026 16:15Проксировать в апстрим всё, чего не нашлось локально - потенциально небезопасно. Разработчик опечатался и вот он уже тянет заботливо подложенный в апстрим mycorp-comon вместо локального mycorp-common. Приходится анализировать по маскам "похоже на внутренние - ищи локально или бросай 404".

devitway_pavel Автор
11.04.2026 16:15Всё верно. Нужный механизм уже реализовывал, правда, с Нексусом, перед которым отлавливали все обращения и сличали с тем, что есть в разрешенных списках, только после этого давали возможность скачать. В этом софте такая фича будет, но чуть позже.

atatarn
11.04.2026 16:15Если позволите, ещё понужу: хочется уметь блокировать пакеты по имени+версии или хешам. Если известно, что использованный кем-либо внутри пакет преисполнился дюжиной CVE - хочется намертво его забанить, а не просто поднять логи кто его скачал.
Успехов в Вашем начинании!

TyurinAS
11.04.2026 16:15Спасибо за статью, и за проект.
Возник вопрос - как масштабировать?
Правильно ли я понял что приложение масштабируется просто - кол-вом инстансов + rwm хранилище? Какие вообще сценарии масштабирования тестировались?

devitway_pavel Автор
11.04.2026 16:15RWM с несколькими инстансами не поддерживается, можете попробовать пока масштабирование чтения через S3 + CDN/кэш перед NORA.
HA в roadmap.
TyurinAS
11.04.2026 16:15А производительность тестировали?

devitway_pavel Автор
11.04.2026 16:15Формального нагрузочного тестирования пока не проводил, вот завёл ишуйку: https://github.com/getnora-io/nora/issues/130

plastid
11.04.2026 16:15Пробовал неделю назад 0.4 с docker и pypi, работало всё и правда шустро.
Документация на сайте не поспевала до актуальной версии, но в целом не проблема.
А вот S3 (RustFS) как storage у меня совсем не взлетел, сегодня попробую ещё раз и сделаю нормальный issue

devitway_pavel Автор
11.04.2026 16:15Спасибо за обратную связь. Вот здесь описал то, что у вас, скорее всего, было и как настроить https://getnora.dev/ru/configuration/s3-storage/

Haiden
11.04.2026 16:15Я правильно понял, что эту штуку можно развернуть локально и при некоторых настройках оно будет кэшировать то, что dockerfile пытается тащить из внешних сервисов?
Тогда бы неплохо добавить поддержку mise

devitway_pavel Автор
11.04.2026 16:15Всё верно вот посмотрите описание конкретного функционала
https://getnora.dev/ru/configuration/docker-proxy/
По mise - пока сложно сказать, посмотрите в сторону Raw.

AvastON
11.04.2026 16:15А возможна ли в будущем поддержка следующих форматов?
- Terrafrom registry (hosted и proxy);
- Ansible galaxy (proxy).
devitway_pavel Автор
11.04.2026 16:15Да, оба формата в планах.
github.com/getnora-io/nora/issues/133
github.com/getnora-io/nora/issues/134

tarkhil
11.04.2026 16:15Выглядит интересно. Хоть кто-то начал делать систему не от максимального количества свистелок

abagnale
11.04.2026 16:15Кладбище альтернатив
Смело вы их всех похоронили. А вот ещё из живых:
Мы недавно себе тоже выбирали, куда сбежать от JFrog Artifactory, которые каждый год стоимость подписки увеличивали на всю большую и большую сумму. Правда, дальше Gitea особо ничего не стали смотреть, потому что на бесплатном Free / Open Source self-hosted варианте все наши форматы пакетов есть, так что про остальных кандидатов из списка ничего сказать не могу, но выглядят они вполне бодро.

devitway_pavel Автор
11.04.2026 16:15Спасибо за дополнение, справедливое замечание.
Gitea - отличный выбор, если команда уже на нём сидит и форматы совпадают. Для вашего кейса (уйти от JFrog и не платить) совершенно логичное решение.
NORA про другой сценарий: отдельный реестр без Git-сервера вокруг.
Пара вещей, которые в Gitea пока не завезли:
- Проксирование upstream-реестров - NORA кэширует пакеты из Docker Hub, npmjs, PyPI локально. В Gitea это открытый тикет https://github.com/go-gitea/gitea/issues/21223 с 2022 года до сих пор не реализовано.
- Большие образы - в Gitea пуш образов больше 800 МБ падает с ошибкой 500, и это актуально для свежей v1.25.5 (https://github.com/go-gitea/gitea/issues/36945).
В NORA ограничение только по диску.
- Очистка мусора - multi-arch образы в Gitea никогда не удаляются, пользователи сообщают о терабайтах мусора (https://github.com/go-gitea/gitea/issues/32053). Автоочистка без тегов - открытый тикет с 2022 года (https://github.com/go-gitea/gitea/issues/21673).
Вишенка - архитектурное решение. Реестр в Gitea - это плагин внутри монолита. Авторизация через токен в CI до сих пор ломается (https://github.com/go-gitea/gitea/issues/23642). Контейнерный реестр нельзя сделать приватным (https://github.com/go-gitea/gitea/issues/24174). В отдельном реестре этих проблем нет по определению,соответственно, падение реестра не утащит за собой Git-сервер.
Если Gitea у вас для кода и пакеты не нагружают, тогда нет вопросов, всё ок. Но если реестр под нагрузкой в CI, закономерно, что стоит держать его отдельно. NORA стартует за 3 секунды, ест 12 МБ памяти и не роняет ваш Git-сервер.
По остальным из списка: Cloudsmith и Buildkite - облачные сервисы, другая категория. ProGet - больше Windows/.NET мир. RepoFlow - 2 тысячи убитых енотов в год.Добавлю Gitea в сравнение, спасибо за наводку.
https://alternativeto.net/software/nora-registry/
abagnale
11.04.2026 16:15Да, там у Gitea (и, скорее всего, у Forgejo тоже), на самом деле, есть ещё ряд проблем, как-то: нельзя указать свой PGP ключ для подписи APT/deb пакетов (генерируется новый по умолчанию и поменять его никакого API нет), какие-то косяки с видимостью Conan пакетов для разных платформ в пределах одной и той же версии пакета, и другие особенности. Ну за бесплатно можно и потерпеть, да.
Собственно, если бы в Норе были APT/deb и Conan, я бы уже бежал ставить, волосы назад.
chemtech
А где ссылка на репозиторий? Думаю стоит расписать побольше о проекте
ikashapov
Похоже вот этот: https://github.com/getnora-io/nora