Rust Docker Tutorial
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:
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
.
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