Skip to main content
When you run npx checkly test or npx checkly deploy, the CLI packs your Playwright tests into a code bundle together with the files your package manager needs: package.json files, the lockfile, and .npmrc. Before each run, Checkly installs your dependencies from that bundle, fetching packages from the registries your project uses. That install fails when your project depends on private packages from an internal registry that Checkly can’t reach, such as an Artifactory, Nexus, or Verdaccio instance behind your firewall or VPN, or an intranet-only mirror of the public npm registry. This page covers the CLI options for those setups.
If your private registry is reachable from the internet and only needs a token, such as GitHub Packages or a hosted npm organization, you don’t need to embed or route packages. Configure your .npmrc as described in Private packages.
Choose the option that matches your setup: All of these options apply to Playwright Check Suites only, not to Browser Checks or Multistep Checks. You configure them at the top level of checkly.config.ts, not per check.

Embed private packages

If some of your dependencies come from a private registry that Checkly’s infrastructure can’t reach, list them in bundle.packages.embed. The CLI downloads those packages on the machine where you run it, verifies each tarball against the integrity hash in your lockfile, and ships them inside the code bundle. The runner then installs them without contacting your registry.
checkly.config.ts
This config embeds every package in the @acme scope that your lockfile contains, except @acme/public-sdk, which the runner must then fetch itself. If your .npmrc sends the whole @acme scope to your internal registry, route @acme/public-sdk to a registry the runner can reach with runner.registries. It also embeds version 2.1.0 of the unscoped legacy-private-pkg. Every other package installs from its usual registry. Each entry is one of:
  • A package name, such as '@acme/private-utils'. Embeds every version of the package found in the lockfile.
  • An exact version pin, such as 'legacy-private-pkg@2.1.0'. Checkly lists only the embedded versions of an embedded package, so a pin must cover every version of that package in your lockfile. Otherwise the install can fail; npm, for example, can’t find the missing version. checkly test and checkly deploy warn when an entry leaves versions of a package in your lockfile unembedded. Embed the package by name to include every version in your lockfile.
  • A name pattern, such as '@acme/*', optionally prefixed with ! to exclude packages that earlier entries selected.
List every package the runner can’t fetch, including private packages that are only dependencies of other private packages. Embedding a package doesn’t embed its dependencies; see Dependencies of dependencies.

Supported lockfiles

The CLI resolves entries against the lockfile at the workspace root: Yarn Berry lockfiles don’t record npm tarball hashes, so for Yarn Berry the CLI looks them up in your registry’s package metadata each time it bundles your checks. Downloaded tarballs are still cached.

Validation

The CLI validates the list before deploying:
  • An entry that matches no package in the lockfile fails validation and blocks the deploy. Only list packages your project actually uses.
  • An entry whose only matches can’t be embedded also fails validation. Packages installed from git, a local path, or a URL, and lockfile entries without an integrity hash, can’t be embedded; the runner must fetch them itself.
  • Workspace members that a pattern matches are skipped, because they’re bundled as source code rather than embedded. An entry that matches only workspace members fails validation.
  • A ! exclusion that matches nothing is ignored.
  • A list that ends up selecting no packages at all only prints a warning.

Registry credentials

Embedded packages need registry credentials only on the machine running the CLI, which downloads them. The runner installs them from the code bundle and never contacts your registry for them, so you don’t need to store your registry token in Checkly; a token reference in the bundled .npmrc whose variable isn’t set in Checkly doesn’t break the install. The CLI reads credentials from:
  • The project, workspace root, and user .npmrc files
  • npm_config_* environment variables
  • pnpm’s global auth.ini, where pnpm login stores tokens on pnpm 11 and later
Scope tokens to the registry (//registry.example.com/:_authToken=${NPM_TOKEN}) rather than setting a bare _authToken. Because .npmrc is uploaded with the code bundle, reference tokens through environment variables instead of writing them in plain text, and set those variables wherever you run checkly test or checkly deploy. Only packages the runner fetches itself need their token stored in Checkly, as described in Private packages. If you use runner.registries, set those tokens in its auth entries instead. If you use bun or Yarn Berry and your credentials only live in bunfig.toml or .yarnrc.yml, copy them into .npmrc or set them as npm_config_* variables; the CLI doesn’t read those files. bun and Yarn Berry projects must also keep their registry URLs, including scoped registries, in .npmrc. If a download fails to authenticate, the error names the credential source the CLI used.

Code bundle size

The Checkly API accepts code bundles of up to 30 MB, compressed. Embedded packages ship inside the code bundle, so embedding large packages, or many packages that add up, can push the bundle over that limit. checkly test, checkly pw-test, and checkly deploy then fail when they upload the bundle:
Terminal
To stay under the limit, embed only the packages the runner can’t fetch from a registry it can reach, and let lockfile pruning and bundle.packages.prune drop the packages your checks don’t use. Packages that pruning removes from the bundled lockfile aren’t embedded. Enterprise plans can raise the limit to 100 MB. Contact your account executive or contact us in the Checkly Web App.

Pruning

Pruning reduces what the runner installs to what your code bundle needs. This keeps internal packages your checks never use out of the install, so you don’t have to embed them or make their registry reachable. The CLI never modifies the files on disk; it only rewrites the copies in the code bundle.

Lockfile pruning

In a monorepo, your code bundle often contains only some of the workspace members. Installing from the full lockfile would make the runner fetch dependencies of members that aren’t in the bundle, and fail if any of those come from a registry it can’t reach. The CLI therefore prunes the bundled lockfile to the bundle’s contents automatically. You don’t need to configure anything. Lockfile pruning supports the same lockfiles as embedding, and requires your package manager to be installed on the machine running the CLI. pnpm and npm reuse their local caches and only contact your registry for packages they haven’t cached; Yarn Berry and bun prune offline. If pruning is needed but can’t run or can’t verify its result, the CLI ships the original lockfile unchanged and prints a note explaining why. Embedding follows the pruned lockfile: a package that only workspace members outside the bundle depend on is not embedded, even if an entry matches it, because the runner never installs it. Control lockfile pruning with these environment variables:
If a package you need stops being embedded after pruning, the workspace member that depends on it is probably not part of the bundle. Import that member from your test code instead of turning pruning off.

Prune manifest dependencies

Use bundle.packages.prune to remove dependencies from the package.json files in the code bundle: the workspace root’s and every bundled workspace member’s. The CLI rewrites the manifests before it prunes the lockfile, so the removed packages drop out of the bundled lockfile too. Besides the peer dependency case below, you can use it to speed up installs. The most common use is unused peer dependencies. With pnpm’s auto-install-peers setting, which is on by default, a member’s peer dependencies end up in the lockfile even when your tests never import them. If those peers come from a registry Checkly can’t reach, the install fails:
checkly.config.ts
This config removes every entry under peerDependencies from the workspace root’s and each bundled member’s package.json. When the CLI then prunes the lockfile, the peers that were only installed to satisfy those entries drop out, so the runner doesn’t try to fetch them. prune accepts two forms:
An array of package name patterns. Matching packages are removed from every dependency class of every bundled package.json:
checkly.config.ts
This removes every @acme package except @acme/runtime, plus storybook, from dependencies, devDependencies, peerDependencies, and optionalDependencies.
Unlike embed, prune patterns don’t accept version pins. A pattern that matches nothing is ignored. Removing a peer dependency also removes its peerDependenciesMeta entry. Patterns only match dependencies declared in package.json files; see Dependencies of dependencies.

Target specific workspace members

To prune only some workspace members, use member-scoped entries in the array form. member is a pattern list matched against each manifest’s name; use '.' for the workspace root. Each entry has exactly one of remove or keep, which take a pattern list or a per-class object:
checkly.config.ts
The first entry removes the peer dependencies of @acme/web and leaves every other member’s peers in place. The second entry reduces @acme/design-system to a single dependency, @acme/tokens: its other dependencies and all of its devDependencies, peerDependencies, and optionalDependencies are removed.
  • remove works like the global patterns, but only for the matched members. Entries apply in the order you list them.
  • keep declares a member’s entire remaining dependency set. Anything it doesn’t select is removed, classes a per-class keep doesn’t mention are emptied, and no other entry can remove a kept package. keep: [] removes every dependency.
Prefer exact member names with keep, since a wildcard can match a member you add later. The CLI warns when a member selector matches no workspace member, or when a keep pattern keeps nothing in a bundled member.

Requirements and caveats

  • When the bundle contains a lockfile, prune only takes effect if lockfile pruning succeeds in the same run, so the manifests and lockfile stay in sync. Otherwise the original package.json files ship unchanged and the CLI prints a warning. A bundle without a lockfile always ships the pruned manifests.
  • The CLI doesn’t check your code against the pruned dependencies. Don’t remove anything your tests need at runtime, or anything the install itself needs. For example, keep typescript when a package’s prepare or postinstall script compiles TypeScript during the install (see lifecycle scripts).

Route runner installs to other registries

If your .npmrc points at a registry URL that only works on your network, such as a VPN-only mirror, but the same packages are available to Checkly from other registries, use runner.registries instead of embedding. It tells the runner which registries to install from, replacing the registries named in the bundled configuration files:
checkly.config.ts
With this config, the runner installs @acme packages only from https://npm.acme.example.com/, authenticating with the ACME_NPM_TOKEN environment variable, and every other package from the public npm registry. If an @acme package is missing from the Acme registry, the install fails instead of asking the public registry for it.
  • upstreams names the registries the runner may use. A token must be a single ${VARIABLE} reference to a Checkly environment variable, which the runner resolves at install time. Literal tokens are rejected.
  • Once runner.registries is set, the runner ignores the registry credentials and TLS settings, such as strict-ssl and cafile, in your bundled .npmrc, pnpm-workspace.yaml, and .yarnrc.yml. Give every upstream that needs authentication its own auth entry.
  • packages routes package names to upstreams. The first rule whose pattern matches wins. The runner tries the rule’s upstreams in order, but never falls through to a later rule, so private package names aren’t sent to a broader registry. The last rule must be { pattern: '**', ... }.
  • Routing patterns don’t accept ! exclusions or version pins.
  • Embedded packages never go through routing rules: the runner installs them from the code bundle and doesn’t ask any routed registry about them.
runner.registries only affects the install on Checkly. Embedding and lockfile pruning still run on your machine and use your .npmrc credentials.

Dependencies of dependencies

Your lockfile records every package the install needs, including dependencies of your dependencies (transitive dependencies). Each option handles them differently:

Embed private dependency chains

The runner installs the dependencies of an embedded package like any other package, from the registries your project uses. That works for public dependencies, but if an embedded package depends on other private packages from the same unreachable registry, you must embed those too. The CLI doesn’t detect a missing one; the install on Checkly fails when it can’t fetch it. For example, if @acme/ui depends on the private @acme/tokens and the public react, embed both private packages and let the runner fetch react from npm:
checkly.config.ts
If your private packages share a scope, a wildcard such as @acme/* embeds the whole scope at every depth, including private packages you only depend on indirectly.

Examples

Monorepo with an internal registry

Your tests live in one member of a pnpm workspace, and your .npmrc points the @acme scope at a registry that’s only reachable from your network:
design-system depends on the private @acme/tokens and declares the private @acme/analytics as a peer dependency, which the tests don’t use. admin depends on the private @acme/billing-sdk.
packages/e2e/checkly.config.ts
When you run npx checkly deploy, the CLI:
  1. Bundles e2e and design-system, because the tests import design-system. admin is left out.
  2. Removes @acme/analytics from the bundled copy of design-system’s package.json.
  3. Prunes the bundled lockfile to the bundle’s contents. @acme/billing-sdk drops out because only admin needs it, and @acme/analytics drops out because nothing declares it anymore.
  4. Embeds the @acme packages that remain in the pruned lockfile, here @acme/tokens, downloading them through your .npmrc. The pattern also matches the workspace members, such as design-system; those are bundled as source code and skipped.
On Checkly, the runner installs @acme/tokens from the bundle and all public packages from npm. It never contacts your internal registry. The prune entry isn’t required for the install to work: without it, @acme/* would also embed @acme/analytics. Pruning the unused peer saves downloading and shipping a package the tests never load. It becomes required when the runner can neither install the peer from the bundle nor fetch it itself, for example a peer installed from an internal Git server.

Private scope behind a VPN-only mirror

Your .npmrc sends every install through a mirror that’s only reachable on your VPN. The mirror proxies the public npm registry and also hosts your @acme packages:
.npmrc
checkly.config.ts
The two options split the work:
  • embed downloads the @acme packages through the mirror on your machine, where the VPN is available, and ships them in the code bundle.
  • runner.registries sends every other package to the public npm registry instead of the mirror. Embedded packages take priority over routing rules, so the @acme packages never reach the ** rule.
Run checkly test and checkly deploy with the VPN connected, so the CLI can download the embedded packages.

Package name patterns

bundle.packages.embed, bundle.packages.prune, and runner.registries share the same pattern syntax: Exclusions only remove packages selected by entries before them, so order matters: ['@acme/*', '!@acme/legacy'] selects the scope except @acme/legacy, while ['!@acme/legacy', '@acme/*'] selects the whole scope.

Troubleshooting

See what was embedded or pruned

Run the CLI with debug logging to see which packages each embed entry matched, and why lockfile pruning was skipped:
Terminal

The code bundle is too large to upload

The compressed bundle exceeds the limit the Checkly API accepts for your account: 30 MB, unless it has been raised. Enterprise plans can raise it to 100 MB. Embedded packages count toward it; see Code bundle size.

Dependencies are reinstalled after a change

Changing the set of embedded packages, the pruned manifests or lockfile, or runner.registries invalidates the runner’s dependency cache. The next run installs dependencies from scratch. A prune pattern or a ! exclusion that matches nothing changes none of these, so it leaves the cache untouched.