npm, behind a firewall
Carry npm packages
across an air gap
One command. Every dependency it needs, checksum verified, in tarballs any registry can import.
Run it in your browserMIT licensed. No account. --dry-run fetches nothing.
- 3
- packages in react-dom
- 80
- packages from one package.json
- 27 to 10
- esbuild, pinned to Linux
$ packall react-dom@19.2.8 --dry-run
Checking registry…
Resolving 1 spec…
resolved 3 packages in 3s
Dry run — nothing downloaded. 3 packages would be bundled:
react-dom → 19.2.8 (3 packages in closure)
Re-run without --dry-run to fetch them:
packall react-dom@19.2.8By hand
npm pack gives you one package. The rest is walking the graph yourself, and verifying none of it.
With packall
It walks the graph, checks every tarball against what the registry published, and writes a manifest naming every version.
What lands on the other side
The whole tree
react-dom is one name and three tarballs. You get all three.
Nothing lands unverified
A checksum mismatch discards the download and stops the run.
Nothing to rewrite
Packages sit in the layout every registry already serves.
Only your platforms
esbuild is 27 packages. Seventeen are binaries for machines you do not have.
Your registry, your credentials
Scoped registries and tokens come from your .npmrc.
Every dependency it needs, verified, in a layout your registry already understands.
Three commands. Only the middle one touches the network.
$ packall react-dom@19.2.8 --dry-run01 Look first
Reports the whole tree without fetching a byte.
$ packall react-dom@19.2.8 --out ./bundles02 Bundle it
Downloaded once, checksum verified, packed with a manifest.
$ tar -xzf react-dom@19.2.8.tgz -C ./bundle03 Carry it over
Unpack and upload into Artifactory, Nexus, Verdaccio or any npm registry.
The proof is what it printed
Every terminal block on this page is a real run, pasted as it came back. Here is the platform claim, twice.
$ packall esbuild@0.25.12 --dry-run
Checking registry…
Resolving 1 spec…
resolved 27 packages in 1s
Dry run — nothing downloaded. 27 packages would be bundled:
esbuild → 0.25.12 (27 packages in closure)
Re-run without --dry-run to fetch them:
packall esbuild@0.25.12$ packall esbuild@0.25.12 --platform linux --dry-run
Checking registry…
Resolving 1 spec…
resolved 10 packages in 1s
Dry run — nothing downloaded. 10 packages would be bundled:
esbuild → 0.25.12 (10 packages in closure)
Re-run without --dry-run to fetch them:
packall esbuild@0.25.12 --platform linuxAnd a whole project, pinned to its lockfile:
$ packall --file package.json --prod --dry-run
Checking registry…
Resolving 3 specs…
resolved 80 packages in 1s
pinned by ./package-lock.json (npm, lockfileVersion 3)
Dry run — nothing downloaded. 80 packages would be bundled:
express → 5.2.1 (66 packages in closure)
pino → 9.14.0 (13 packages in closure)
zod → 3.25.76 (1 package in closure)
Re-run without --dry-run to fetch them:
packall --file ./package.json --prod --lockfile ./package-lock.jsonQuestions
Does the restricted network need to reach npm?
No. You run packall where the registry is reachable, carry the tarballs across however you move files, and import them on the other side.
How is this different from npm pack?
npm pack gives you one package without its dependencies. packall walks the whole closure, verifies every checksum, and records what it did.
What is inside a bundle?
Package tarballs in the layout every registry serves, plus bundle-manifest.json naming each version, source and the edge that pulled it in.
Can I bundle a whole project?
Point --file at a package.json, a lockfile, or a list of approved names. A lockfile beside it pins the run, and the output says so.
Does it work with Artifactory, Nexus or Verdaccio?
Both directions. Registries and tokens come from your .npmrc. Proxy settings are the one thing it does not read.
What if a checksum does not match?
The download is discarded and the run stops rather than warning and carrying on.
Will it include the binaries the target machine needs?
By default, for every platform. Pass --platform when you know the target and want the rest left behind.
How do I run it in CI?
--yes takes every default, --json writes a summary to stdout, --quiet drops everything but warnings and errors.
What does it cost?
Nothing. MIT licensed, no account, no telemetry.
Nothing to commit to
MIT licensed. No account, no telemetry, no paid tier. --dry-run shows you what a command would fetch without fetching it.