The Cargo Guide

Vendoring, Offline, and Hermetic Builds

Why Vendoring and Offline Workflows Matter

Most small Rust projects assume the network is available and crates can be fetched when needed. Enterprise, release-engineering, and high-reliability environments often cannot make that assumption. They may need guaranteed dependency availability, controlled provenance, air-gapped builds, or reproducible release pipelines.

A useful mental model is:

  • ordinary Cargo workflows assume dependencies can be fetched when necessary
  • vendored and hermetic workflows make dependency availability an explicit part of the build system

The Core Problems These Workflows Solve

Vendoring, offline builds, and hermetic builds usually exist to solve one or more of these problems:

  • the build machine has no internet access
  • the organization wants strict dependency provenance control
  • CI or release systems must not depend on live public infrastructure at build time
  • the team wants stronger guarantees that a build will still work later even if external services are unavailable

A useful distinction is:

  • offline is about not using the network during a build
  • hermetic is about making the build self-contained and controlled

What Vendoring Means in Cargo

Vendoring means copying dependency sources into a local directory inside or alongside the project so Cargo can use them without fetching from the network.

Cargo supports this with cargo vendor.

Example:

cargo vendor

By default this vendors all crates.io and git dependencies for the project into a local vendor directory and prints the configuration needed to use those vendored sources.

What cargo vendor Produces

cargo vendor does two important things.

First, it creates a local directory containing dependency sources.

Second, it prints Cargo configuration to standard output that tells Cargo to use those vendored sources.

A useful mental model is:

  • the vendor directory is the local source snapshot
  • the printed config is the switch that tells Cargo to use it

A Minimal Vendoring Workflow

A typical starting workflow looks like this:

cargo vendor > .cargo/config.toml```
 
This both creates the vendor directory and writes the required source-replacement configuration into the project's Cargo config.
 
That configuration is what makes subsequent builds use the vendored sources instead of reaching out to crates.io or git remotes.

A Small Example Project

Suppose you start with a simple package:

cargo new hermetic_demo
cd hermetic_demo

Manifest:

[package]
name = "hermetic_demo"
version = "0.1.0"
edition = "2024"
 
[dependencies]
serde = { version = "1", features = ["derive"] }
regex = "1"

Then vendor dependencies:

cargo vendor > .cargo/config.toml```
 
Now the project has a local vendored dependency snapshot and configuration telling Cargo to use it.

What the Vendor Config Looks Like

The configuration emitted by cargo vendor is source-replacement config. A representative form looks like this:

[source.crates-io]
replace-with = "vendored-sources"
 
[source.vendored-sources]
directory = "vendor"

A useful mental model is:

  • dependencies still conceptually come from crates.io
  • Cargo is told to satisfy that source from the local vendored directory instead

Vendored Sources Are Read-Only

Cargo treats vendored sources as read-only, just like normal registry and git sources. That means vendoring is for reproducible source availability, not for interactive editing of dependency code.

If you want to modify a dependency during development, Cargo's guidance is to use [patch] or a path dependency instead of editing the vendored copy.

A useful mental model is:

  • vendor for controlled source availability
  • patch or path-override for live dependency debugging

Why Vendoring Is Not the Same as Local Dependency Hacking

A common mistake is to think the vendored directory is a convenient place to patch dependencies manually. It is usually better to treat vendored content as a build input cache rather than as an editable working copy.

For real debugging or temporary forks, use an override such as:

[patch.crates-io]
serde_json = { path = "../serde_json" }

That keeps the workflow explicit and compatible with incremental rebuilds.

Offline Mode

Cargo supports offline operation with --offline.

Examples:

cargo build --offline
cargo test --offline
cargo vendor --offline```
 
In offline mode, Cargo is prevented from accessing the network. It will attempt to proceed using only locally available dependency data.
 
A useful mental model is:
 
- offline means no network access at build time
- success still depends on the required dependencies already being available locally

Why Offline Mode Can Behave Differently

Cargo's docs warn that offline mode can produce different dependency resolution behavior than online mode, because Cargo restricts itself to crates already downloaded locally, even if newer compatible versions are known in the local index metadata.

That means offline mode is not just online mode without downloads. It is a stricter resolution environment.

Preparing for Offline Builds with cargo fetch

Cargo provides cargo fetch to download dependencies ahead of time.

Example:

cargo fetch```
 
If a `Cargo.lock` file is present, Cargo fetches the dependencies needed for that lockfile. If no lockfile exists, Cargo generates one first and then fetches.
 
A practical offline workflow is:
 
```sh
cargo fetch
cargo build --offline```

Why cargo fetch Is Important

cargo fetch is one of the simplest ways to prepare a machine or CI cache for offline use.

A useful mental model is:

  • cargo fetch populates local dependency availability
  • --offline enforces network absence

Together they make offline workflows much more predictable.

Locked Mode

Cargo supports --locked to require that the existing lockfile remain unchanged.

Examples:

cargo build --locked
cargo test --locked
cargo vendor --locked```
 
`--locked` causes Cargo to fail if the lockfile is missing or if resolution would require changing it.
 
A useful mental model is:
 
- `--locked` protects the exact dependency graph shape recorded in `Cargo.lock`
- it is about deterministic dependency selection, not about network behavior by itself

Frozen Mode

Cargo supports --frozen, which is equivalent to using both --locked and --offline.

Examples:

cargo build --frozen
cargo test --frozen
cargo vendor --frozen```
 
A useful mental model is:
 
- `--locked` means do not change the lockfile
- `--offline` means do not use the network
- `--frozen` means enforce both at once

What Hermetic Means in Practice

A hermetic build is more than merely offline. It usually means the build is deliberately constrained so that inputs are explicit and controlled.

In a Cargo context, a strongly hermetic workflow often includes:

  • a committed or otherwise controlled Cargo.lock
  • vendored or mirrored sources
  • no live network access during the actual build
  • predictable toolchain and configuration

A useful mental model is:

  • offline is one ingredient
  • hermetic is the larger build-governance model

A Simple Hermetic Build Pattern

A small hermetic-friendly pattern might look like this:

cargo vendor > .cargo/config.toml
cargo build --frozen```
 
This uses vendored sources, disables network access, and prevents lockfile drift.
 
It is not the only hermetic pattern, but it is one of the clearest entry points.

Source Replacement and Vendoring

Vendoring relies on Cargo's source-replacement system. Source replacement tells Cargo to satisfy one source from another source that is intended to be equivalent.

For vendoring, that usually means replacing crates.io with a local directory source.

Example:

[source.crates-io]
replace-with = "vendored-sources"
 
[source.vendored-sources]
directory = "vendor"

A useful mental model is:

  • the dependency declarations stay normal
  • source-replacement changes where Cargo gets the source content

Private Mirrors

Another common enterprise pattern is using a private mirror instead of direct public registry access.

Example .cargo/config.toml:

[source.crates-io]
replace-with = "company-mirror"
 
[source.company-mirror]
registry = "sparse+https://mirror.example.com/index/"

This lets Cargo dependency declarations continue to look like ordinary crates.io usage while redirecting the actual fetches to a controlled internal mirror.

Mirror vs Vendor

A useful practical distinction is:

  • a mirror redirects dependency fetching to another registry-like source
  • vendoring copies the dependency sources into a local directory under your control

Mirrors are often better for centralized enterprise infrastructure. Vendoring is often better for per-repository or air-gapped distribution of dependencies.

Air-Gapped Builds

Air-gapped builds are an extreme offline case where the build environment cannot access outside networks at all.

Cargo supports this well when the required dependencies are made available ahead of time through vendoring, prefetching, local registries, or controlled mirrors that are themselves reachable in that environment.

A practical air-gapped pattern is:

1. prepare dependencies in a connected environment
2. transfer vendor or registry data into the air-gapped environment
3. build with --frozen```

A Small Air-Gapped-Oriented Workflow

A simple repository-oriented air-gapped workflow might look like this in a connected environment:

cargo vendor > .cargo/config.toml```
 
Then in the disconnected environment:
 
```sh
cargo build --frozen```
 
This works because the vendor directory and lockfile provide the dependency inputs that the disconnected system needs.

Enterprise and Release Engineering Concerns

Enterprise and release-engineering teams often care about things beyond simple build success.

Typical concerns include:

  • dependency provenance and auditability
  • resilience against upstream outages
  • deterministic release pipelines
  • long-term rebuildability
  • centralized control over dependency sources
  • minimizing supply-chain surprises

This is why vendoring and mirrors are often release-engineering decisions, not just developer conveniences.

Dependency Availability Guarantees

The deeper goal of vendoring and hermetic workflows is dependency availability guarantees.

A useful mental model is:

  • ordinary builds assume dependencies will probably be reachable
  • hermetic builds aim to guarantee that the needed dependency set is already available under controlled conditions

That guarantee usually comes from some combination of:

  • lockfiles
  • vendored sources
  • local registries
  • mirrors
  • controlled caches

A Practical CI Pattern

A simple CI pattern for stronger dependency guarantees might look like this:

cargo fetch
cargo build --locked
cargo test --locked```
 
And a more hermetic variant might look like this:
 
```sh
cargo vendor > .cargo/config.toml
cargo build --frozen
cargo test --frozen

The right pattern depends on whether the CI system is merely controlled or truly isolated.

Workspaces and Vendoring

Vendoring can be applied at workspace scope, which is especially useful for monorepos and coordinated release environments.

Example workspace root:

[workspace]
members = ["app", "core"]
resolver = "3"

Then:

cargo vendor > .cargo/config.toml

vendors the dependencies needed for the selected workspace context, and Cargo's emitted config can be kept at the workspace root so all members share the same vendored source policy.

Syncing Multiple Manifests

Cargo vendor supports additional manifests through --sync.

Example:

cargo vendor --sync ../other-workspace/Cargo.toml

This is useful when one vendor directory should cover more than the default current manifest selection, such as in coordinated multi-workspace environments.

Versioned Vendor Directories

Cargo vendor supports --versioned-dirs to include versions in directory names more broadly.

Example:

cargo vendor --versioned-dirs

This can make vendor history and re-vendoring behavior easier to reason about, especially when only some dependencies change over time.

No-Delete Vendoring

Cargo vendor also supports --no-delete.

Example:

cargo vendor --no-delete

This keeps existing contents in the vendor directory instead of deleting them first. It can be useful in workflows where the vendor directory is being updated incrementally and deletion behavior is not desirable.

Respecting Existing Source Config

By default, cargo vendor ignores some existing [source] configuration when downloading crates. It supports --respect-source-config when you want it to use existing source configuration such as mirror setup.

Example:

cargo vendor --respect-source-config

This is especially relevant in enterprise environments that already have a source-replacement or mirror policy in place.

What Hermetic Does Not Automatically Solve

Even a vendored or frozen build does not automatically solve every reproducibility problem. Toolchain version, system linkers, native build prerequisites, and environment-sensitive build scripts can still affect outcomes.

A useful mental model is:

  • vendoring controls dependency source availability
  • full reproducibility may still require toolchain and environment discipline too

Common Beginner Mistakes

Mistake 1: assuming --offline alone guarantees dependencies are available.

Mistake 2: treating vendored sources as editable dependency working copies.

Mistake 3: using source replacement for one-off debugging when [patch] would be the correct tool.

Mistake 4: forgetting that --frozen implies both lockfile stability and no network access.

Mistake 5: assuming mirrored builds and vendored builds are the same thing.

Mistake 6: calling a build hermetic when only the network has been disabled but dependency and toolchain inputs are still loosely controlled.

Hands-On Exercise

Create a small project and make it progressively more controlled.

Start here:

cargo new vendoring_lab
cd vendoring_lab

Add a dependency:

[dependencies]
serde = { version = "1", features = ["derive"] }

Then try these steps:

cargo fetch
cargo build --offline
cargo vendor > .cargo/config.toml
cargo build --frozen

After that, inspect the vendor config and vendor directory. Then imagine the same repository being copied into an isolated environment and ask which parts of the build are now guaranteed locally and which parts still depend on toolchain or system assumptions. That exercise is one of the fastest ways to understand the difference between ordinary, offline, and hermetic Cargo workflows.

Mental Model Summary

A strong mental model for vendoring, offline, and hermetic Cargo builds is:

  • cargo vendor creates a local dependency source snapshot and emits source-replacement config
  • cargo fetch prepares local dependency availability for later offline use
  • --offline disables network access but still depends on local availability
  • --locked prevents lockfile drift
  • --frozen enforces both locked and offline behavior
  • mirrors and vendoring solve related but different dependency-availability problems
  • hermetic builds are about controlled inputs, not only about lack of network access

Once this model is stable, Cargo's offline and hermetic workflows become much easier to treat as dependency-governance tools rather than just special command flags.