cargo vendor is the tool for managing offline Rust builds, but it’s not magic; it relies on your system’s existing knowledge of where to find your dependencies.
Let’s see cargo vendor in action. Suppose you’re building a project my_rust_app that depends on rand and serde.
First, you’ll need a Cargo.toml file:
[package]
name = "my_rust_app"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5"
serde = { version = "1.0.197", features = ["derive"] }
Now, you want to vendor these dependencies. You’d typically run cargo vendor in your project’s root directory. But for this to work, your cargo needs to know where to get these crates. This is where config.toml comes in.
By default, cargo looks for dependencies on crates.io. If you’re offline, it can’t reach that. You need to tell it to look elsewhere, or to download them to a local directory.
Consider this config.toml in your ~/.cargo/ directory:
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index" # This is the default, but explicit is good.
# If you want to point to a *local* mirror or a private registry, you'd change this URL.
# For offline, we'll use a local directory.
[source.local-vendored]
directory = "/path/to/your/vendored/crates" # This is where cargo vendor will put them.
Now, when you run cargo vendor, it will:
- Read your
Cargo.tomlto identify dependencies (rand,serde). - Consult your
config.toml. It seescrates-ioas the primary source. - If
crates-iois unreachable, it will try to resolve dependencies from other configured sources. cargo vendor’s primary job is to download the specified versions of your dependencies into the directory specified by[source.local-vendored].directory. It doesn’t configure cargo to use that directory by default for future builds.
To make your build truly offline, you need to tell cargo to use your local vendored directory instead of crates.io. This is done by modifying your Cargo.toml temporarily or by using cargo’s configuration system.
The most straightforward way for an offline build is to tell cargo to use the local directory for the build:
cargo build --config "source.crates-io.replace-with = 'local-vendored'"
This command tells cargo to substitute any requests for crates-io with requests to your local-vendored source during this specific build.
Let’s walk through the process:
-
Initial Setup (Online):
- Create your project:
cargo new my_rust_app - Add dependencies to
my_rust_app/Cargo.toml:[package] name = "my_rust_app" version = "0.1.0" edition = "2021" [dependencies] rand = "0.8.5" serde = { version = "1.0.197", features = ["derive"] } - Ensure you have a
~/.cargo/config.tomlthat defines your local vendored directory. Let’s assume it’s~/.cargo/config.tomlwith:
(It’s often cleaner to use a path relative to your project or home directory).[source.local-vendored] directory = "my_vendored_crates" # Relative to your home directory, or an absolute path
- Create your project:
-
Vendoring (Online or Offline, but needs initial download):
- Run
cargo vendor. This command will download the specified versions ofrandandserdeinto yourmy_vendored_cratesdirectory. You’ll see output like:Downloading rand v0.8.5 Downloading serde v1.0.197 Downloading serde_derive v1.0.197 ... - After this, your
my_rust_app/directory will have amy_vendored_crates/directory containing the source code for your dependencies.
- Run
-
Offline Build:
- Now, disconnect from the internet.
- Build your project using the
--configflag:cargo build --config "source.crates-io.replace-with = 'local-vendored'" cargowill attempt to resolverandandserde. It seescrates-iois requested, but your configuration tells it toreplace-with 'local-vendored'. It then looks in~/.cargo/config.tomlfor[source.local-vendored], finds thedirectory = "my_vendored_crates", and pulls the sources from there.
The mental model here is that cargo vendor is a downloader, and the config.toml (or --config flag) is the instruction manual for cargo on where to look for crates during a build. cargo vendor populates the "local-vendored" directory; the --config flag tells cargo to use that directory.
The truly surprising thing is how cargo handles multiple sources. It doesn’t just try them in order; it has a sophisticated resolution mechanism. If you have multiple [source] entries in your config.toml, cargo will try to find the exact version you requested across all of them. If a dependency is found in a local source and crates.io, cargo will typically prefer the one that matches the version requirement most precisely, or based on internal weighting. This means simply adding a local source doesn’t automatically override crates.io unless you explicitly tell it to, or if crates.io is unavailable.
The next step for more complex offline scenarios is managing transitive dependencies and ensuring your vendored set is complete for all build targets and configurations.