# Fast Rust Docker Builds with "cargo vendor"


Recently, I was working on a small Rust application that I wanted to run in a
Docker container. Rust binaries can take a long time to compile, especially if
you have a large number of dependencies. So, Docker's built-in caching comes in
handy to reduce build times.

With Docker caching, you want to do as much work up-front as possible, so that
you only need to redo compilation as necessary. For us, this means that we want
to save our compiled dependencies in the Docker cache so that we only recompile
our own code each time we iterate.

Many tutorials I saw online suggested the following workflow:

1. Create a new "dummy crate" within the Docker container using `cargo init`.
2. Copy your `Cargo.toml` / `Cargo.lock` files into the Docker container.
3. Run `cargo build` to build the dummy (essentially empty) `main.rs` created by
   `cargo init`.
4. Copy your _actual source_ into the Docker container (overwriting the dummy
   crate).
5. Re-run `cargo build` to compile your project's real source code.

In the above process, Docker can cache all the steps up to step 4. As long as
you don't make changes to your dependency list, the only thing that will need to
be recompiled is _your_ source code.

However, creating a dummy crate as part of the build feels like a hack.
Furthermore, in some configurations you need to manually delete intermediate
build artifacts for this workflow to work. Gross. Until recently, Cargo didn't
support building crate's dependencies independently of the binary/library, which
made using a dummy crate the simplest way to take advantage of the Docker cache.

Fortunately, `cargo vendor` was created and solves just this issue: it allows
you to vendor your dependencies into a local directory so that dependencies can
be cached by Docker. Though `cargo vendor` is now part of the main Cargo
project, it's still worth checking out its
[original repo](https://github.com/alexcrichton/cargo-vendor) for more details.

The workflow with `cargo vendor` achieves the same cache performance, but is (in
my opinion) a lot cleaner:

1. Copy your `Cargo.toml` / `Cargo.lock` files into the Docker container.
2. Run `cargo vendor` to download your project's dependencies.
3. Copy your project source into the Docker container.
4. Re-run `cargo build` to compile your project.

Once again, we're able to cache everything up until the point that we copy in
our source code. However, with this flow we don't have to worry about creating a
dummy crate.

An example `Dockerfile` using `cargo vendor` looks something like this:

```docker
# -----------------
# Cargo Build Stage
# -----------------

FROM rust:1.39 as cargo-build

WORKDIR /usr/src/app
COPY Cargo.lock .
COPY Cargo.toml .
RUN mkdir .cargo
RUN cargo vendor > .cargo/config

COPY ./src src
RUN cargo build --release
RUN cargo install --path . --verbose

# -----------------
# Final Stage
# -----------------

FROM debian:stable-slim

COPY --from=cargo-build /usr/local/cargo/bin/my_binary /bin

CMD ["my_binary"]
```

**Note:** We pipe the output of `cargo vendor` into `.cargo/config` so that in
subsequent builds, Cargo recognizes that we've already vendored our
dependencies. The output of `cargo vendor` looks like this:

```
[source.crates-io]
replace-with = 'vendored-sources'

[source.vendored-sources]
directory = '/usr/src/app/vendor'
```

We've replaced the `crates.io` source with our own local source that points to
the `vendor/` directory in which `cargo vendor` built our dependencies.

---

So, now we have a quicker way to build Rust Docker containers without resorting
to unfortunate hacks. Furthermore, by using multistage Dockerfiles, like the one
presented above, we can dramatically reduce the size of Rust container images.
👏

Happy containerizing!

**Edit (12/9)**: The downside of this approach is that `cargo vendor` only
_downloads_ your dependencies, whereas the hacky solution both downloads _and
builds_ your dependencies. So, there's still room for improvement. 😕 There's a
[pretty old issue](https://github.com/rust-lang/cargo/issues/2644) on cargo
requesting a `--dependencies-only` flag to `cargo build`, but it's unclear if
that's on cargo's roadmap.

[Discussion on Reddit](https://www.reddit.com/r/rust/comments/e6mft0/fast_rust_docker_builds_with_cargo_vendor/)

