Skip to content
AndyShiuPublic

About

Self-hosted artifact repository for 25 package formats — a drop-in replacement for Sonatype Nexus. Single Go binary, every feature, no paid tier.

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

Holiaokho 好料庫

hó-liāu-khòo — Taiwanese for the place you keep the good stuff

A self-hosted artifact repository for 25 package formats.
Built to replace Sonatype Nexus without asking anyone to change how they work.

English · 繁體中文 · holiaokho.andyshiu.com

License Security Release Image

Holiaokho stores the packages your builds depend on and the artefacts they produce — Maven, npm, Docker/OCI, PyPI, NuGet and twenty more — behind one address, one binary and one PostgreSQL database.

Why this instead of what you have

Switching does not hurt. Repository paths, the Docker port connectors and the credential model follow Nexus conventions. A production cut-over ran on the same host and the same ports without changing one line of CI configuration.

It is small. A single Go binary. No JVM, no plugins, no application server. It starts serving in seconds and runs comfortably in a few hundred megabytes — under real CI load, with several pipelines pulling at once.

It keeps working when upstream does not. Cached artefacts are still served while a public registry is unreachable, so a bad afternoon at Maven Central does not stop your builds. Upstreams that keep failing are blocked automatically instead of making every request wait out a timeout.

Nothing is held back for a paid tier. There is no paid tier.

Quick start

docker compose -f deploy/docker-compose.yml up -d

Then open http://localhost:8081/ui/. The first login uses the password from HOLIAOKHO_ADMIN_PASSWORD, and the account cannot do anything else until that password is changed — a password somebody else chose is a password too many people know.

Point a client at it exactly as you would at Nexus:

# Maven: settings.xml
<mirror><id>holiaokho</id><url>http://localhost:8081/repository/maven-public/</url><mirrorOf>*</mirrorOf></mirror>

# npm
npm config set registry http://localhost:8081/repository/npm-group/

# Docker (path mode, no extra port needed)
docker pull localhost:8081/docker-hub/alpine:3.19

A fresh install creates the same starter repositories Nexus would, plus a Docker group on port 8082 so a daemon's registry-mirrors has somewhere to point.

What it looks like

Dashboard showing health checks, repository counts and scheduled tasks

Repository management, and browsing cached content
Repository list with format, type, online state and URL for each Browsing the cached contents of a proxy repository

How it fits together

flowchart LR
    subgraph clients [Your build tools]
        direction TB
        mvn[Maven]
        npm[npm]
        dkr[Docker]
        etc[22 more]
    end

    subgraph holiaokho [Holiaokho]
        direction TB
        api["One address<br/>/repository/NAME"]
        hosted[(hosted<br/>what you publish)]
        proxy[(proxy<br/>upstream cache)]
        group[(group<br/>merged view)]
        api --> hosted
        api --> proxy
        api --> group
        group -.-> hosted
        group -.-> proxy
    end

    subgraph state [State]
        direction TB
        pg[(PostgreSQL<br/>metadata)]
        blob[(Filesystem or S3<br/>content-addressed blobs)]
    end

    upstream([Maven Central<br/>npm registry<br/>Docker Hub…])

    clients --> api
    proxy -->|on a miss| upstream
    holiaokho --> state
Loading

Metadata lives in PostgreSQL; file contents live in a content-addressed blob store, so the same bytes referenced by ten repositories occupy the disk once. A proxy repository serves what it has cached and only reaches upstream on a miss — which is why it keeps working when upstream does not.

What it does

25 package formats, each verified against its official client running in Docker (scripts/e2e-formats.sh): Maven, npm, Docker/OCI, PyPI, raw, NuGet, Helm, Go, APT, YUM, Alpine, RubyGems, Cargo, Composer, Conda, R/CRAN, p2, CocoaPods, Terraform, pub, Git LFS, Hugging Face, Ansible Galaxy, Conan, Swift. Hosted, proxy and group for every one of them. A group takes deployments too: mvn deploy, npm publish, twine upload or docker push to the group lands in its first hosted member, so a build needs one URL for both directions. The caller needs write permission on the group and on that member.

Access control — local accounts (argon2; Nexus Shiro hashes accepted on import), LDAP, OIDC, reverse-proxy header auth. Roles read as targets times actions, down to a single repository; content selectors narrow that further by path. User tokens, login rate limiting, a password policy.

Operations — routing rules, cleanup policies with a preview, soft delete and compacting GC, storage quotas, cron-scheduled tasks, webhooks with HMAC signing, e-mail, an audit log, backup and restore including blobs, Prometheus metrics.

Supply chain — the OCI referrers API. Signatures, SBOMs and build attestations that cosign and syft attach to an image are stored as what they are, and found again through the same endpoint the tools already ask. Proxy repositories answer from upstream and cache the result; the web interface lists what is attached to each image.

Vulnerability scanning — stored packages are checked against OSV for known vulnerabilities: Maven, npm, PyPI, Go, NuGet, RubyGems, Cargo, Composer, pub and CRAN. One row per actual vulnerability (GHSA, CVE and ecosystem ids merged), worst first, with the versions that fix it. Newly found critical and high findings are sent once by email and webhook and flagged on the dashboard. Formats OSV does not cover are reported as not covered — never as clean. On by default; any repository can opt out. Findings export as a report: PDF or Excel to read, CSV or JSON — with purls — for scripts and AI agents checking a project's own dependencies.

Malicious packages — downloads of packages OSV lists as malicious (the OpenSSF malicious-packages database) are refused before a build can run them, from the cache and on the way in from a proxy alike. Every block is recorded and the first is sent by email and webhook; an administrator can let a wrongly listed version through, with the reason on record.

Storage — local filesystem or any S3-compatible service, content-addressed and reference-counted, so an identical file referenced by ten repositories occupies the disk once.

A web interface — in Traditional Chinese, Simplified Chinese, English, Japanese and Korean, with a dark mode that was actually looked at.

Configuration

Every key in config.example.yaml can be set as a HOLIAOKHO_* environment variable instead. Kubernetes manifests are in deploy/k8s/.

Outbound connections. Besides the upstreams you configure, the server makes two kinds of request on its own. Both can be turned off, and both go through the same outbound proxy and CA settings as everything else:

What Where Sends Turn off
Vulnerability scanning, and checking a package on its first download api.osv.dev package names and versions HOLIAOKHO_VULNERABILITIES_ENABLED=false (the download check alone: HOLIAOKHO_VULNERABILITIES_BLOCK_MALICIOUS=false)
New-release check, daily api.github.com a User-Agent with the version, nothing else HOLIAOKHO_UPDATES_CHECK=false

Flaky upstreams. A download that fails on a dropped connection, a timeout or a 429/502/503/504 is tried again twice, with a short backoff, before the client sees an error; an upstream's Retry-After is honoured up to five seconds. Set proxy.retries (HOLIAOKHO_PROXY_RETRIES) to change the default, or override it per repository.

Documentation

Security

Every push is scanned, and the results are public — the badge above links to them rather than asking you to take this section's word for it.

Gitleaks Credentials, across the full commit history rather than the current tree
Trivy Dependency vulnerabilities, secrets, and Kubernetes/Dockerfile misconfiguration
Trivy The published container image
govulncheck Go vulnerabilities that this code actually reaches, not just version matches
staticcheck, go vet Static analysis

Containers run as a non-root user with a read-only root filesystem and every Linux capability dropped — verified by running them that way, not only by passing a scanner.

A clean scan is not the same as being secure. docs/security.md describes the trust boundaries, what is protected, and what is knowingly not.

Contributing

Bug reports and patches are welcome. See CONTRIBUTING.md; pull requests need a signed CLA.

Security problems should go through GitHub's private vulnerability reporting rather than a public issue — see SECURITY.md.

License

Apache License 2.0. Copyright 2026 Pei-En Hsu.

About

Self-hosted artifact repository for 25 package formats — a drop-in replacement for Sonatype Nexus. Single Go binary, every feature, no paid tier.

Topics

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages