Why self-host a private npm registry on your VPS
Three situations make a private npm registry essential. The first is managing internal JavaScript libraries: if you develop a design system, business utilities or API clients specific to your organization, publishing them to npmjs.org exposes them to the whole internet. A private registry keeps them behind your infrastructure, versioned and accessible only to your developers and pipelines.
The second is CI pipeline resilience. npmjs.org experiences outages several times a year, and each one can block your deployments for hours. Verdaccio caches every downloaded public package: the second npm install — in CI or locally — never hits the network again. Your builds speed up and become independent of the public registry's availability.
The third is offline or air-gapped work. Once the cache is pre-populated, Verdaccio serves all previously seen packages without an internet connection — useful for strict compliance environments, on-premise pipelines or demos without Wi-Fi.
Verdaccio addresses all three in a single lightweight Docker container (under 128 MB RAM), with no external database and no complex configuration. It implements the full npm protocol, so npm, Yarn and pnpm connect with a single configuration parameter, without touching any dependency.
What you gain with self-hosted Verdaccio
- Private packages under your namespace — publish internal libraries without exposing them to the public registry.
- Local npmjs.org cache — your CI builds keep running even when the public registry is unavailable.
- Zero external dependencies — a single Docker container, one volume, no database.
- Under 128 MB RAM — runs on the smallest VPS without impacting other services.
- Compatible with npm, Yarn, and pnpm — no client changes needed, just update the registry URL.
- Offline mode — pre-warm the cache once, disconnect internet: builds stay reproducible.
Requirements
Verdaccio is one of the lightest services you can deploy on a VPS. The prerequisite list is intentionally short: a VPS with 1 vCPU, 512 MB RAM (Verdaccio uses under 128 MB in normal operation) and Docker installed. No domain is required to start — you access the registry via an SSH tunnel from your workstation or a CI runner.
For permanent team or hosted CI pipeline access, attach a domain from your ServOrbit dashboard: nginx will proxy it over HTTPS automatically. Verdaccio works equally well behind this reverse proxy, and the TLS certificate is managed by ServOrbit without any action on your part.
For package storage, plan disk space proportional to your usage: public packages cached from npmjs.org and your private packages accumulate in a persistent Docker volume. For a team of 5 to 10 developers, a few gigabytes cover most JavaScript projects.
Deploy Verdaccio on your ServOrbit VPS
Order from the ServOrbit Marketplace
From your ServOrbit client area, install Verdaccio in one click from the Marketplace: select the Development category, choose Verdaccio, and confirm your order. The Docker container starts automatically on your VPS with a built-in healthcheck. You will receive a notification as soon as the service is operational, with the assigned exposure port.
Connect via SSH tunnel
Without a configured domain, open an SSH tunnel from your local machine:
ssh -L 4873:127.0.0.1:<port> root@<vps-ip>. Then point npm at the tunnel:npm set registry http://localhost:4873. For Yarn 2+ (Berry), addnpmRegistryServer: "http://localhost:4873"to your.yarnrc.yml. For pnpm:pnpm config set registry http://localhost:4873. These settings can be applied globally (~/.npmrc) or per project (.npmrcat the repository root).Create your first account
Run
npm adduser --registry http://localhost:4873. Enter a username, password and email address. This first user becomes the registry administrator with full read and publish rights. You can create additional accounts with the same command — each developer gets their own credentials and access token.Publish your first private package
In your package directory, run
npm publish --registry http://localhost:4873. Make surepackage.jsoncontains anamefield with your private scope (e.g.@your-org/my-package) to distinguish it from public packages. Verify the publication athttp://localhost:4873: your package appears in the list with its version number. It is now available to any authenticated developer or CI pipeline.Attach a domain for permanent access
From your ServOrbit client area, attach a domain or subdomain to your VPS. nginx will automatically proxy Verdaccio over HTTPS. Update your global
.npmrc(registry=https://verdaccio.your-domain.com) or the CI environment variableNPM_CONFIG_REGISTRY. Your GitHub Actions, GitLab CI or other runners can now access the registry without an SSH tunnel.
For permanent team access, attach a domain from your ServOrbit dashboard. nginx automatically proxies Verdaccio over HTTPS — set your global .npmrc (registry=https://verdaccio.your-domain.com) or define NPM_CONFIG_REGISTRY in your CI environment variables.
Configure scopes and proxy rules
By default, Verdaccio applies a single rule: every package is first searched locally, then proxied to npmjs.org if absent. This rule works for getting started, but most teams want to refine behavior by scope.
Configuration lives in config.yaml, accessible from the Docker volume (verdaccio-storage). The packages: section defines rules by name pattern. For a private scope (@acme/*), you can disable the proxy to npmjs.org (proxy: '') and allow only authenticated users to read and publish. For all other packages (**), you leave the proxy to npmjs active.
Minimal scope configuration example:
packages:
'@acme/*':
access: $authenticated
publish: $authenticated
'**':
access: $all
proxy: npmjsAfter modification, restart the container: docker compose restart verdaccio. The configuration is applied without reloading the package database. This approach ensures your private packages never reach npmjs.org, even in case of a configuration error.
CI/CD pipeline integration
Using Verdaccio in your CI/CD pipelines brings two key benefits: build stability (independence from npmjs.org) and access to private packages from runners.
For GitHub Actions, set the environment variable in your workflow:
jobs:
build:
env:
NPM_CONFIG_REGISTRY: https://verdaccio.your-domain.com
NPM_CONFIG_//verdaccio.your-domain.com/:_authToken: ${{ secrets.VERDACCIO_TOKEN }}For GitLab CI, the equivalent syntax goes in the variables: section of your .gitlab-ci.yml. The access token is obtained with npm token create --registry https://verdaccio.your-domain.com from any Verdaccio user account.
For public package caching only (no private packages), you can configure Verdaccio as a npmjs.org mirror in a dedicated runner Docker image. Packages are downloaded once by the first build, then served from cache for all subsequent builds — node_modules installation drops from 30–60 seconds to 2–5 seconds.
Security and access control
By default, Verdaccio uses its own authentication system based on an htpasswd file. Accounts created with npm adduser are stored there with a bcrypt hash. This mechanism is sufficient for a small team, but Verdaccio also supports third-party authentication plugins: LDAP, GitLab, GitHub OAuth, Keycloak and others.
For CI access tokens, use npm token create --registry https://verdaccio.your-domain.com rather than storing a plain-text password. Tokens can be revoked individually without affecting other users.
On ServOrbit, Verdaccio is bound to 127.0.0.1 and exposed only through the nginx reverse proxy. Port 4873 is never directly accessible from the internet — nginx adds TLS, and only the proxied domain responds on port 443. For maximum security, enable mandatory read authentication (access: $authenticated in config.yaml) if your registry contains sensitive packages. By default, Verdaccio allows anonymous reading — acceptable for a public package cache, but should be restricted for proprietary packages.