Every team eventually needs an artifact registry. You need somewhere to push Docker images, host internal npm packages, or cache Maven dependencies so your builds don't break when a mirror goes down. The standard answer is Nexus or Artifactory. Both work. Both are also Java applications that need a JVM, a database, careful heap tuning, and at least 2 GB of RAM before they'll serve a single artifact. On a CI server that's already running builds, that memory budget hurts.
One developer decided the problem was simpler than the existing solutions make it look. The result is a 32 MB Rust binary that handles seven package protocols on less than 100 MB of RAM.
What Is Nora?
Nora is a lightweight artifact registry built by devitway (Pavel Volkov). It supports Docker/OCI, Maven, npm, PyPI, Cargo, Go modules, and raw file hosting in a single binary. It includes a web UI dashboard, Prometheus metrics, token auth with Argon2id password hashing, S3 or local storage backends, mirror/proxy mode for air-gapped environments, and garbage collection. You configure it with a TOML file and run it. That's it.
36 stars. Three months of focused solo development. About 19,600 lines of Rust across 45 source files. This project is doing real work and almost nobody knows about it.
The Snapshot
| Project | Nora |
| Stars | 36 at time of writing |
| Maintainer | Solo (devitway) |
| Code health | 411 tests, proptest, fuzz targets, 61.5% coverage, CI that would make a team project jealous |
| Docs | Good README, CONTRIBUTING.md with build/test/PR instructions |
| Contributor UX | Same-day review on PRs, warm and specific feedback |
| Worth using | Getting there. Mirror mode and auth are solid. Watch this space. |
Under the Hood
The architecture is what you'd hope for: each registry protocol gets its own module under src/registries/, storage is abstracted behind a trait (local filesystem or S3), and the HTTP layer is axum with tokio. Config is validated at startup. There's no clever metaprogramming, no macro soup. You can open the Docker registry module and understand what it does without reading three layers of indirection first.
The dependency list is restrained for a project this ambitious. axum and tokio handle the HTTP server. reqwest handles upstream proxy requests. serde and toml for config. The entire Cargo.toml reads like someone who picks dependencies on purpose rather than pulling in whatever shows up first on crates.io. For context: Nexus Repository Manager pulls in over 400 Maven dependencies. Nora's Cargo.lock has about 350 crate entries, but most of those are transitive deps from tokio and reqwest. The direct dependency count is small.
The CI pipeline is where Nora stands out from other solo projects. Most one-person repos have a test workflow and maybe clippy. Nora runs: cargo fmt, clippy with -D warnings, the full test suite, cargo-audit for vulnerability scanning, cargo-deny for license and supply-chain policy, Trivy for container scanning, Gitleaks for secret detection, CodeQL for static analysis, and OpenSSF Scorecard for security posture. That is not a typical solo developer setup. It's more thorough than plenty of team projects I've contributed to.
The test suite backs it up. 411 #[test] functions across 29 files. Proptest for parser fuzzing. Fuzz targets via cargo-fuzz with ClusterFuzzLite integration. Integration tests that spin up the actual binary and exercise all seven protocols. Playwright end-to-end tests for the web UI. Coverage measured at 61.5% via tarpaulin.
Security is taken seriously too. Credentials use Argon2id with zeroize to scrub secrets from memory after use. The token verification layer has a TTL cache so it's not re-hashing on every request. There are explicit TOCTOU race condition fixes in the storage layer and request deduplication for the proxy mode so concurrent pulls for the same image don't stampede the upstream registry.
What's rough? The web UI modules are untested. ui/api.rs (1,010 lines), ui/templates.rs (861 lines), and ui/components.rs (783 lines) have zero test coverage between them. That's 2,654 lines of code running the dashboard with no safety net. There's also a dead code problem: error.rs defines a full AppError type with an IntoResponse impl that's been sitting unused since v0.3. The comment says "wiring into handlers planned for v0.3" but they're on v0.4 now, and handlers still construct status code responses manually. The garbage collector has a subtler issue: collect_all_blobs scans all seven registries, but collect_referenced_digests only reads Docker manifests. Non-Docker blobs would look like orphans to the GC. None of these are deal-breakers, but they're the kind of gaps that matter as the project grows.
The Contribution
I was reading through the metrics code when I noticed detect_registry() had match arms for Docker, Maven, npm, PyPI, and Cargo, but Go and Raw requests fell through to an "other" catch-all. Every request to those two registries was invisible in Prometheus. The RegistriesHealth struct had the same gap: five fields for five registries, but Go and Raw weren't represented. The health endpoint would report them as down even when they were running fine.
The kicker was the test suite. There was already a test for Go registry path detection. It asserted that a Go module request should be labeled "other". The test was passing because it was checking for the wrong thing.
The fix was straightforward: add the missing match arms in detect_registry(), add the go and raw fields to RegistriesHealth and its construction in check_registries_health(), and fix the test to assert the correct label. PR #97.
Getting into the codebase was easy. The CONTRIBUTING.md lays out build and test commands clearly. The module structure maps directly to concepts: if you want to understand how Docker pushes work, you open src/registries/docker/. If you want metrics, you open src/metrics.rs. I found the bugs by reading, not by fighting the project layout. The whole thing compiled and tested cleanly on the first try.
This was the first external PR the project had ever received. The maintainer reviewed it, approved it, and merged it the same day. His comment:
"This is the very first community PR that NORA has ever received, and it means a lot. The fact that you not only noticed the missing Go and Raw registries in metrics, but took the time to write a clean fix with proper tests... Welcome to the team."
That response tells you something about whether this project has a future.
The Verdict
Nora is for teams that need artifact hosting without the Java tax. If you're running a small engineering team, managing a homelab, or working in an air-gapped environment where you can't reach public registries, a 32 MB binary that handles seven protocols on 100 MB of RAM is a compelling alternative to configuring Nexus heap flags.
The project's trajectory is strong. January was the initial scaffold with Docker support. February added six more protocols. March brought security hardening, proptest, integration tests, and a coverage push from 22% to 61.5%. April shipped v0.4 with mirror mode and air-gap support. That's a lot of ground to cover in three months, and the commit history tells a consistent story: conventional commits, one concern per commit, no monolithic dumps.
What would push Nora to the next level? Wire in the AppError type so error responses are consistent across registries. Fix the GC so it doesn't treat non-Docker blobs as orphans. Get some test coverage on the UI modules. And keep doing what's already working, because the foundation is solid.
Go Look At This
If you've ever winced at your artifact registry's memory usage, give Nora a look. The codebase is clean, the CI is thorough, and the maintainer merges good work the same day you submit it.
Star the repo. Try running it against your Docker workflow. If you want to contribute, the AppError wiring and the GC's Docker-only reference scanning are both waiting for someone to pick them up.
This is Review Bomb #N, a series where I find under-the-radar projects on GitHub, read the code, contribute something, and write it up. If you know a project that deserves more eyeballs, drop it in the comments.
Top comments (0)