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:

  1. Read your Cargo.toml to identify dependencies (rand, serde).
  2. Consult your config.toml. It sees crates-io as the primary source.
  3. If crates-io is unreachable, it will try to resolve dependencies from other configured sources.
  4. 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:

  1. 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.toml that defines your local vendored directory. Let’s assume it’s ~/.cargo/config.toml with:
      [source.local-vendored]
      directory = "my_vendored_crates" # Relative to your home directory, or an absolute path
      
      (It’s often cleaner to use a path relative to your project or home directory).
  2. Vendoring (Online or Offline, but needs initial download):

    • Run cargo vendor. This command will download the specified versions of rand and serde into your my_vendored_crates directory. 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 a my_vendored_crates/ directory containing the source code for your dependencies.
  3. Offline Build:

    • Now, disconnect from the internet.
    • Build your project using the --config flag:
      cargo build --config "source.crates-io.replace-with = 'local-vendored'"
      
    • cargo will attempt to resolve rand and serde. It sees crates-io is requested, but your configuration tells it to replace-with 'local-vendored'. It then looks in ~/.cargo/config.toml for [source.local-vendored], finds the directory = "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.

Want structured learning?

Take the full Cargo course →