Rust Docker Tutorial

Elliot Forbes Elliot Forbes ⏰ 5 Minutes 📅 May 24, 2022

Welcome all! In this tutorial, we are going to be looking at a few different ways that we can effectively dockerize our Rust applications and run them as containers.

I’m making more of an effort these days to learn Rust and expand my skillset, as a result, I am going to be writing more articles based on Rust as a method for helping myself learn and hopefully help demystify topics for other people in a similar position.

Why is Containerization Important?

Now, before we dive in, we should have a think about the reasons behind containerizing our applications.

If you are writing complex web apps or command-line tools, having a reproducible environment in which you can build and run your application helps to simplify the operational-side to building and releasing your software.

You are able to capture all key things like the version of Rust you wish to build your app with, or perhaps any environment variables your app will need on startup in a Dockerfile which you can then build and run on any machine that support Docker.

A Single Stage Dockferfile

Let’s start off simple and layer on the complexity once we are able to build and run our app.

We’ll start by defining what is known as a single-stage Dockerfile which handles both the building and running of our Rust app.

First, create a new file called Dockerfile at the root of your Rust application.

Let’s start off by defining the base image. In this case, we’ll want to use the official rust image and we can specify 1.61 which is the latest version of Rust currently available at the time of writing this article:

Dockerfile
FROM rust:1.61

Next, we’ll want to copy all of our source files over into our Dockerfile using the COPY command:

COPY . .

With the source files now available within our image, we can then try and run the cargo build command which compiles our Rust source into a binary. We’ll pass the --release flag as an argument to our cargo build command to instruct the cargo tool that we are building our app for production.

RUN cargo build --release

Finally, with our application built as a binary executable, we need to run it. We’ll need to ensure that the name of the executable lines up with the name we have defined within our cargo.toml file. In the sample repo, the cargo.toml has the package name docker and as such, the binary will be named the same:

CMD ["./target/release/docker"]

Perfect, we have now crafted our single-stage Dockerfile and it’s ready to be built as an image. Let’s fire up our terminal and run the docker build command:

$ docker build -t my-rust-app .

This may take a few minutes, but once it has successfully completed, we can then attempt to run our newly baked docker image using the run command:

$ docker run -it my-rust-app
Hello, world!

Great success! We have been able to containerize and run our Rust application using a single-stage Dockerfile.

A Multi-stage Dockerfile

Now that we have a strong foundation, let’s layer on a little more complexity and add multiple stages to our Dockerfile.

Dockerfile
FROM rust:1.61 AS builder
COPY . .
RUN cargo build --release

FROM debian:buster-slim
COPY --from=builder ./target/release/docker ./target/release/docker
CMD ["/target/release/docker"]

Now, by adding these lines, we have been able to strip down our finished docker image so that it only includes the bare minimum needed to run our application.

The debian:buster-slim image is a very popular, barebones image that a lot of developers tend to use when picking a lightweight production image. There are certainly a few other alternatives out there though, so if this one isn’t to taste, then you can certainly swap it out for something that does suit you and your requirements better.

Let’s attempt to build this fancy new multi-stage dockerfile now using the same command as before.

We’ll change the names slightly so that we can easily make the distinction between the two images from this article:

$ docker build -t multistage-rust-app .

Finally, with this built, let’s run it again using the same commands as before:

$ docker run -it multistage-rust-app
Hello, world!

Our new fancy multi-stage dockerfile has been built and run. The only difference from the single-stage dockerfile is that our finished running image is drastically smaller in size which is perfect for running in production!

Alternative Production Base Images

Another base image you can consider for your multi-stage Dockerfiles is the Distroless image. You can see an example of a multi-stage dockerfile for rust within Google’s official repo: Distroless Multistage Dockerfile

Incremental Caching and Improving Performance

Note - these tips were provided by the /r/rust

There are a few strategies we can incorporate into our Dockerfiles in order to improve the speed of our builds.

One such method is using sccache in order to help cache certain parts of our build. You can check out this article on using Sccache to speedup Rust builds in Docker by Mitch describes this approach in detail.

Conclusion

Awesome, so in this tutorial, we have looked at how we can build docker images and run them as containers for our Rust-based applications.

If you know of a better way to build and run Rust apps with Docker, please let me know in the comments section down below!

Note - the full source code for this tutorial can be found here - TutorialEdge/rust-lessons