TL;DR / Summary: Small product teams can host a private npm-compatible registry or pull-through cache on one VPS to speed CI and keep internal packages off public scanners. Lock auth down, mirror only what you need, and measure cache hit rates before calling the project finished.
The pain that justifies another box
Public registry blips stall every pipeline at once. Internal packages published to a public scope invite curious downloads. A private registry on a virtual server fixes both problems when the team is still too small for a managed enterprise contract.
You are buying control and latency, not a new religion. If a managed offering already fits the budget, take it. This guide assumes you chose self-host for clear reasons.
Registry vs pull-through cache
A full private registry stores your scoped packages and may proxy public ones. A pull-through cache mainly accelerates and pins public dependencies. Many teams need both behaviors; some only need the cache.
Decide which problem hurt last quarter. Do not install features for imaginary future monorepos.
- Internal libraries → private registry with auth
- Flaky public downloads in CI → cache
- Both → registry that can uplink carefully
VPS placement and TLS
Put the VPS in the region nearest your CI runners. Enable TLS with a real certificate. Package managers will talk to this host thousands of times a day; browsers are not the only clients that deserve HTTPS.
Firewall so only office egress IPs and CI IPs can reach the service ports. The public internet does not need to browse your package list.
If anonymous read is on, assume the world will read.
Authentication that survives employee churn
Use token auth per machine user, not a shared password in a wiki. Rotate tokens when people leave. Store publish tokens only on the CI role that is allowed to publish.
Developers can read with narrower tokens. Publish should be boring and rare.
Storage growth plans
Package tarballs accumulate. Mount a dedicated volume, monitor inode and byte usage, and set retention for unused cached public packages. Internal release history may need longer retention — decide explicitly.
Snapshots weekly are cheap insurance against a bad upgrade of the registry software.
Backup is not the same as cache
You can rebuild a public cache. You cannot shrug away lost private tarballs of a hotfix from Tuesday. Back up private storage first.
Client configuration without tribal knowledge
Ship a short internal doc: registry URL, scope mapping, and how to request a token. Add a template .npmrc fragment to the eng handbook.
If onboarding still requires a DM to the one person who “knows Verdaccio,” the project is incomplete.
Measuring whether it worked
Track cache hit ratio, p95 download time in CI, and disk growth weekly. If hit ratio stays low, your uplink rules or CI cold starts need attention — not a bigger VPS.
If disk growth is linear and steep, retention is wrong.
Upgrade discipline
Pin the registry image version. Test upgrades on a snapshot-restored twin when possible. Read changelogs for auth breaking changes before you click update on Friday.
Keep a rollback command in the README. Future you is a person.
When you outgrow a single node, revisit managed options or a tiny active-passive pair. Until then, one well-run VPS beats three neglected ones. A clear overview of VPS hosting helps you re-shop CPU and disk without rewriting the app.
Publish workflow that prevents “laptop releases”
Forbid publishing from developer laptops once CI publish works. Laptop releases skip logs and produce packages nobody can reproduce. The registry should accept publish tokens only from the CI egress IPs when your firewall can express that.
Add a chat notification on every publish with package name, version, and pipeline link. Silent publishes are how surprise breakages travel.
Read-only mirrors for contractors
Contractors often need install access without publish rights. Issue short-lived read tokens and revoke them on contract end dates already in the calendar. Do not share the internal publish token “just for this afternoon.”
If contractors must be off-VPN, put the registry behind SSO-aware access rather than opening the world.
Package provenance notes for audits
Keep a lightweight provenance log: who published, which pipeline, which git SHA. When a dependency CVE appears, you need minutes-to-answer, not archaeology. Store the log off the registry disk as well as on it.
If your registry software supports signed metadata, turn it on after a staging test. Unsigned internal packages become a trust story you cannot tell customers.
Rate limits and CI stampedes
Monday mornings can stampede the mirror when every laptop and runner refresh locks. Set rate limits that shed load gracefully and teach developers to use project-local caches. A mirror that melts weekly is worse than no mirror.
Stagger scheduled dependency refresh jobs across the hour.
When the mirror returns 429s, document the client backoff you expect in the eng handbook so every language ecosystem does not invent a different retry storm.
Sunset conditions
Write down what success looks like and what triggers migration: team size, compliance needs, or multi-region latency. Predetermined exits keep the registry from becoming an unpaid second product.
Done carefully, a private registry VPS is boring infrastructure — the highest compliment in packaging.
Upstream outages and stale tarball risks
A mirror that keeps serving last week’s tarball during an upstream outage can be useful—or dangerous—if a yanked package remains available. Decide in advance whether the mirror should fail closed on known yanks.
Log package name, version, and fetch time for every cache fill. When security asks “when did we pull that,” the answer should not depend on memory.
