vlt / docs

  • PricingBenchmarks (opens in new window)Community (opens in new window)Feedback
  • Overview
  • vlt
  • npm
  • pnpm
  • yarn
  • bun
  • deno
  • CI & Automation
  • Who reads what
  • GitHub Actions
  • Per package manager
  • Generating .npmrc at runtime instead
  • pnpm needs a different approach
  • Two notes on the other tools
  • Vercel
  • Coding-agent sandboxes
  • Publishing from CI
  • Read-only installs of public packages
  • Checklist
  1. Registry
  2. /
  3. Publishing
  4. /
  5. CI & Automation

CI & automation

Install and publish from CI, Vercel and coding-agent sandboxes with a vlt.io service token.

Store your token as a secret named VLT_TOKEN and expose it to the job as an env var. The config files in these guides reference it (${VLT_TOKEN}, or $VLT_TOKEN in bunfig.toml), so nothing else changes between your laptop and CI — except pnpm, see below.

If CI publishes, the token must be a service token

Personal tokens require an interactive browser confirmation on every publish and will fail with EOTP. See Publishing from CI.

Who reads what

Only the vlt CLI reads VLT_TOKEN itself (with VLT_REGISTRY, or per-registry VLT_TOKEN_*; see Authentication). Other tools see the secret only through an auth line that references it (pnpm: ~/.npmrc or a pnpm_config_… env var), so setting the secret alone sends no credentials.

ToolAuth fromToken reference in committed project file
vltkeychain or env varsn/a, .npmrc not read
npm, npx.npmrcexpanded
pnpm~/.npmrc, or pnpm_config_… env varignored (11.5.3+)
yarn.yarnrc.yml (Classic: .npmrc)expanded
bunbunfig.toml ($VLT_TOKEN) or .npmrcexpanded
deno.npmrcexpanded
Vercel builds.npmrc written from NPM_RCsee Vercel

GitHub Actions

YAML
name: ci
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      VLT_TOKEN: ${{ secrets.VLT_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
      - run: npm ci
      - run: npm test

Commit the .npmrc / bunfig.toml / .yarnrc.yml from the relevant guide. Because they reference ${VLT_TOKEN} rather than a literal token, they are safe in version control and need no rewriting step in CI (pnpm: see below).

Per package manager

Terminal
# npm — commit .npmrc with ${VLT_TOKEN}
npm ci

# pnpm — pin to 11.26.0, and pass the token as a config env var (see below)
env "pnpm_config_//registry.vlt.io/acme/npm/:_authToken=$VLT_TOKEN" \
  pnpm install --frozen-lockfile

# yarn — commit .yarnrc.yml with ${VLT_TOKEN}
yarn install --immutable

# bun — commit bunfig.toml with $VLT_TOKEN
bun install --frozen-lockfile

# deno — commit .npmrc with ${VLT_TOKEN}
deno ci

# vlt — one VLT_TOKEN_<registry> per registry
VLT_TOKEN_https_registry_vlt_io_acme_npm=$VLT_TOKEN \
VLT_TOKEN_https_registry_vlt_io_acme_main=$VLT_TOKEN \
vlt install --frozen-lockfile

Generating .npmrc at runtime instead

If you would rather not commit the file, write it in a step:

YAML
- run: |
    cat > .npmrc <<'EOF'
    registry=https://registry.vlt.io/acme/npm/
    @acme:registry=https://registry.vlt.io/acme/main/
    //registry.vlt.io/acme/npm/:_authToken=${VLT_TOKEN}
    //registry.vlt.io/acme/main/:_authToken=${VLT_TOKEN}
    EOF

Keep the quotes on <<'EOF' so the shell leaves ${VLT_TOKEN} intact for the package manager to expand — never write the literal token into a project .npmrc. pnpm ignores ${VLT_TOKEN} here too; see below.

pnpm needs a different approach

pnpm ignores ${VLT_TOKEN} in a project .npmrc on purpose, so the committed-file pattern above does not carry credentials for it. Commit only the routing, and supply the token as a config environment variable:

YAML
- run:
    env
    "pnpm_config_//registry.vlt.io/acme/npm/:_authToken=$VLT_TOKEN"
    pnpm install --frozen-lockfile

Use env rather than export: the variable name contains /, : and ., which the shell will not accept in an assignment. This needs pnpm 11.6.0 or newer; see the pnpm guide for the full version matrix.

Or write the auth lines to the user-level ~/.npmrc, where pnpm still expands ${VLT_TOKEN}:

YAML
- run: |
    cat >> ~/.npmrc <<'EOF'
    //registry.vlt.io/acme/npm/:_authToken=${VLT_TOKEN}
    //registry.vlt.io/acme/main/:_authToken=${VLT_TOKEN}
    EOF
- run: pnpm install --frozen-lockfile

PNPM_CONFIG_NPMRC_AUTH_FILE=<path> (env form of --npmrc-auth-file) does the same with an auth file outside the repo (verified on pnpm 11.26 and 12).

Pin pnpm by exact version in packageManager, not by range. Corepack resolves a range against its local cache first, so pnpm@11 can produce different versions on different runners.

Two notes on the other tools

  • deno — deno ci (Deno 2.8+) is the equivalent of npm ci: it installs strictly from deno.lock and fails rather than updating it.
  • deno — Deno's default 24-hour dependency-age gate will block a package you published minutes earlier. If CI installs your own fresh releases, configure minimumDependencyAge as shown in the deno guide.

Vercel

Vercel doesn't read VLT_TOKEN. Builds install with an .npmrc Vercel writes from the NPM_RC env var. Set NPM_RC to your routing and auth lines with a package:read service token, and keep a registry.npmjs.org line — Vercel installs its runtimes from the public registry. For multi-line values, set it with the Vercel CLI to keep the line breaks. Use NPM_RC, not NPM_TOKEN (meant for private packages on npm's own registry; NPM_RC wins if both are set). See Vercel's private dependencies guide.

Coding-agent sandboxes

Each cloud agent or dev environment (Cursor, Claude Code, Devin, Codespaces, …) needs the token set up separately — treat each like a CI runner:

  • Give it its own package:read service token (owners and admins create them), so you can revoke one without breaking the others.
  • Store the token in that environment's secrets (these guides call it VLT_TOKEN).
  • Commit the routing: default registry → the npm mirror, @acme scope → main (.npmrc / .yarnrc.yml / bunfig.toml / vlt.json).
  • Provide auth for both registry URLs: committed ${VLT_TOKEN} lines (npm, yarn, deno; $VLT_TOKEN in bunfig.toml), a setup step writing ~/.npmrc (pnpm, above), or, for vlt, the env vars in Authentication.

Publishing from CI

Use a service token. When you create the token at vlt.io → Tokens, choose This is a service token rather than This token is for myself, and give it package:read and package:write.

That choice is what makes unattended publishing work. A personal token answers every publish with a one-time-password challenge and fails in CI with:

Text
npm error code EOTP
npm error This operation requires a one-time password.

A service token has no such challenge — verified with npm, pnpm, yarn, bun and vlt, all publishing to main with no prompt.

YAML
- run: npm publish
  env:
    VLT_TOKEN: ${{ secrets.VLT_TOKEN_CI }}

Nothing else about the config changes; a service token is a drop-in replacement in every snippet in these guides. Store it as a separate secret from your personal token so it is obvious which one CI uses.

Three per-tool notes:

  • yarn needs yarn install before yarn npm publish, or it aborts with This package doesn't seem to be present in your lockfile.
  • pnpm needs --no-git-checks in CI, since the checkout is often not on the publish branch.
  • deno publishes in two steps — deno pack then npm publish <tarball>. Set publish.include in deno.json first, or deno pack may bundle your .npmrc into the tarball. See the deno guide.

If you are stuck with a personal token, vlt publish can take a pre-obtained code:

Terminal
VLT_OTP=<code> vlt publish

Read-only installs of public packages

A job that installs only public packages from main needs no secret at all — just the registry URL:

INI
@acme:registry=https://registry.vlt.io/acme/main/

The npm mirror always requires a token, so this only helps if every one of your dependencies is a public package from main.

Checklist

  • VLT_TOKEN is set as a masked secret, not a plaintext variable.
  • The committed config uses ${VLT_TOKEN} (or $VLT_TOKEN for bunfig.toml; pnpm: routing only) — never a literal token.
  • The secret is really set in the job: npm, bun and deno send an unset token variable literally, which looks like a bad token.
  • After switching registries: npm and pnpm lockfiles kept; vlt-lock.json, yarn.lock, bun.lock and deno.lock regenerated (see each guide).
  • Installs use the frozen-lockfile flag for the package manager.

PreviousdenoNextGetting Started

On this page

  • Who reads what
  • GitHub Actions
  • Per package manager
  • Generating .npmrc at runtime instead
  • pnpm needs a different approach
  • Two notes on the other tools
  • Vercel
  • Coding-agent sandboxes
  • Publishing from CI
  • Read-only installs of public packages
  • Checklist
Edit this page

Deploy your package on vlt.io

Publish scoped and private packages, manage organizations and access, and give every developer and CI environment a consistent source for public and private JavaScript dependencies.

Publish now