Package Management in Air-Gapped Environments
How to manage software packages in networks without internet access — challenges, solutions, and how stout's mirror and private index features help.
Air-gapped networks have no connection to the public internet. They exist in defense (DOD IL5/IL6 enclaves), financial trading floors, healthcare systems handling PHI, industrial control systems (SCADA/ICS), and any environment where data exfiltration or unauthorized code execution poses an unacceptable risk.
If you manage developer tooling or build infrastructure in one of these environments, you already know the core problem: every mainstream package manager assumes it can reach the internet. Homebrew fetches from GitHub. apt pulls from archive.ubuntu.com. pip talks to PyPI. When the network cable to the outside world is physically cut, all of these break.
Here’s how air-gapped package management works, what makes it hard, and how stout’s mirror and private index features address the problem directly.
What makes air-gapped environments different
An air-gapped network isn’t just “slow internet” or “behind a proxy.” It means:
- No outbound HTTP/HTTPS. DNS resolution for external domains fails. There is no proxy, no NAT, no sneaky tunnel.
- Data enters via controlled transfer. Software gets in through burned DVDs, approved USB drives, or a data diode — a one-way hardware device that physically prevents outbound traffic.
- Every artifact must be verified. Because you can’t re-download something if it’s corrupted or tampered with, integrity verification is non-negotiable. You need cryptographic signatures, not just checksums.
- Version pinning is mandatory. You can’t run
brew updateto get the latest. You get exactly what was approved and transferred. If a package has an unresolved dependency, there’s no way to pull it at install time.
These constraints mean that traditional package managers — designed around the assumption of always-on internet — require significant workarounds to function.
The Homebrew approach (and why it breaks)
Homebrew has no official air-gapped mode. To use Homebrew offline, teams typically:
- Clone the homebrew-core tap onto a machine with internet access.
- Download all required bottles (pre-built binaries) manually.
- Transfer the git repo and bottles to the air-gapped network via approved media.
- Set
HOMEBREW_NO_AUTO_UPDATEand point Homebrew at a local file server hosting the bottles.
This works in theory. In practice, it’s fragile:
- homebrew-core is 700MB+. Transferring this across a data diode or on approved media is slow and painful. Updates require transferring git diffs, which means maintaining state on both sides.
- Dependency resolution happens at install time. If you missed a transitive dependency when downloading bottles, the install fails — and you’re back to the transfer process.
- No built-in signature verification. Homebrew bottles are served over HTTPS, which provides transport security. But in an air-gapped context, you’re copying files onto USB drives. There’s no TLS. You need artifact-level signatures to verify that what you transferred is what was built. Homebrew doesn’t provide this.
- Formula evaluation requires Ruby. The Ruby runtime and the full formula DSL must be present on the air-gapped machine. This is a large attack surface for environments that minimize installed software.
How stout mirror works
stout has first-class support for air-gapped environments through its stout mirror command. The workflow is designed around the reality of controlled data transfer.
Step 1: Create a mirror bundle on an internet-connected machine
# On a machine with internet access
stout mirror create \
--packages ffmpeg,imagemagick,redis,node@20,[email protected] \
--platform macos-arm64 \
--output mirror-bundle-2026-04-01.tar
This command resolves all dependencies, downloads every required bottle, snapshots the package index, and bundles everything into a single archive. The bundle includes:
- A self-contained SQLite index with only the relevant package metadata
- All bottles (pre-built binaries) for the specified packages and their transitive dependencies
- Ed25519 signatures for every artifact
- A manifest listing every file with its SHA-256 hash
The output is a single .tar file. For the package set above, it’s typically 200-400MB — compared to the 700MB+ just for Homebrew’s git repo before you even download a single bottle.
Step 2: Transfer the bundle
Move the .tar file through your approved transfer process — data diode, burned disc, approved USB, whatever your security policy requires. Because it’s a single file with a known hash, verification at the boundary is straightforward.
Step 3: Serve the mirror inside the air-gapped network
# On a machine inside the air-gapped network
stout mirror serve --bundle mirror-bundle-2026-04-01.tar --port 8080
This starts a lightweight HTTP server (single binary, no dependencies) that serves the index and bottles. Any machine on the air-gapped network can now point stout at this mirror:
# On developer machines inside the air-gap
stout config set registry http://mirror-host:8080
stout install ffmpeg imagemagick redis
Installation works exactly like it would on the public internet — parallel downloads, dependency resolution, progress output — except all traffic stays within the air-gapped network.
Step 4: Verify signatures
Every package installed from a mirror is verified against its Ed25519 signature. The public key is embedded in the stout binary (and can be overridden for private signing keys). This means:
- Tampering during transfer is detected and rejected
- A compromised mirror server can’t serve modified binaries
- You get artifact-level verification, not just transport-level (TLS) verification
$ stout install ffmpeg --verbose
Fetching [email protected] from http://mirror-host:8080...
Verifying Ed25519 signature... ok
Verifying SHA-256 checksum... ok (a3f8c1d...)
Extracting... done
Private indexes for custom packages
Beyond mirroring public packages, many air-gapped environments need to distribute internal tools. stout supports private indexes that let you host your own packages alongside (or instead of) the public index.
# Create a private index
stout index init --path /srv/stout-index
# Add a custom package
stout index add \
--path /srv/stout-index \
--name internal-tool \
--version 2.1.0 \
--bottle ./internal-tool-2.1.0-macos-arm64.tar.gz \
--sign-key /path/to/private-key.pem
# Serve the private index
stout mirror serve --index /srv/stout-index --port 8080
Developers configure stout to use the private index as their primary (or sole) registry. This gives platform teams full control over what software is available inside the air-gap.
Comparison with other approaches
| Feature | Homebrew (manual) | Artifactory | stout mirror |
|---|---|---|---|
| Bundle creation | Manual scripting | Requires Artifactory instance | stout mirror create |
| Transfer format | Multiple files + git repo | Repository sync | Single .tar file |
| Dependency resolution | At install time | At install time | Pre-resolved in bundle |
| Signature verification | None (artifact-level) | GPG optional | Ed25519 built-in |
| Serving infrastructure | nginx + scripting | JFrog server (Java) | stout mirror serve (3MB binary) |
| Custom packages | Local tap (complex) | Supported | stout index add |
JFrog Artifactory is a capable solution, but it’s a large Java application that requires its own infrastructure, licensing, and maintenance. For teams that just need to get developer tools into an air-gapped network, stout mirror provides the same outcome with a fraction of the complexity.
Updating an air-gapped mirror
When you need to update packages, create a new bundle on the internet-connected side:
stout mirror create \
--packages ffmpeg,imagemagick,redis,node@20,[email protected] \
--platform macos-arm64 \
--diff-from mirror-bundle-2026-04-01.tar \
--output mirror-bundle-2026-04-10.tar
The --diff-from flag creates a delta bundle containing only packages that changed since the previous bundle. This minimizes the data that needs to cross the air gap — often reducing the transfer from hundreds of megabytes to a few megabytes for routine updates.
Air-gapped package management will never be as convenient as brew install on a laptop with gigabit internet. But with the right tooling, it doesn’t have to be a multi-day ordeal involving custom scripts, manual dependency tracking, and crossed fingers. stout mirror is designed to make the process predictable, verifiable, and as close to the normal workflow as the constraints allow.
Need Rust performance engineering or AI agent expertise?
Neul Labs — the team behind stout — consults on Rust development, performance optimization, CLI tool design, and AI agent infrastructure. We build fast, reliable systems that ship.