Deploying a Static Rust App in a Barebones Docker Container

2018-06-19

tutorial

Introduction

This post will cover how to get a simple static Rust executable running inside a barebones Docker container. This allows you to compile static Rust binaries for a single platform (Docker, or more specifically Linux x86), and run them on any operating system which can run Docker. Although Rust already compiles to a lot of platforms , I think this method could still be useful in some cases.

There are already other great blog posts and examples on this topic. This can be seen as a micro-tutorial to get the most basic version working. I highly recommend checking out the other resources to get something actually useful up and running.

Full source code for this post is available on GitHub. As you can see, there's not much to it.

Prerequisites

  • rustup, Rust, and cargo. Note that the best way to install Rust and cargo is using rustup, so you just need to download that and follow the instructions and it will download and install the other two for you.
  • musl libc on your system with the musl-gcc command is on your PATH. musl is a lightweight libc implementation that works well for static linking. It is supported by Rust.
  • Docker installation

Rust stuff

First create a new Rust executable project:

cargo new --bin rust_docker_barebones

Navigate to that directory.

We're going to build our executable in release mode, and tell it to use musl to output a static binary that doesn't depend on any dynamically linked libraries.

First we need to install the musl target for Rust:

rustup target install x86_64-unknown-linux-musl

Then we can build it:

cargo build --release --target=x86_64-unknown-linux-musl

That's it for Rust.

Docker stuff

Create the following Dockerfile in the Rust project directory:

FROM scratch

COPY target/x86_64-unknown-linux-musl/release/rust_docker_barebones /rust_docker_barebones

ENTRYPOINT ["/rust_docker_barebones"]

This starts with the most stripped-down Docker image, called the scratch image. Maybe you've used the excellent tiny Alpine image? This is even more minimal than that. Basically the only thing the scratch image can do is run a Linux x86 executable file. The Dockerfile copies our Rust release binary into the image at the location /rust_docker_barebones. Finally it sets that location as the default executable to call when the Docker container is launched.

Now build the Docker image:

docker build -t rust_docker_barebones .

And finally try running it:

docker run rust_docker_barebones

You should see the default Rust "Hello, World!" output.

And that's it!

Next steps

There's a lot you can do to improve this. Here's a couple ideas:

Optimize Docker image size

The resulting Docker image is >4.5MiB in size. This is mostly due to the Rust executable. Optimizing this is beyond the scope of this post, but one simple step is to run the strip program on the binary to remove debug symbols. A quick test on my system yielded a 539k image size. To go deeper, I'd start with this post.

Make a full app

If you want to deploy something actually useful, check out this post which goes into the details of getting a web service working. You could use that information along with a previous post of mine to deploy "pseudo-desktop" applications across all operating systems capable of running Docker and a web browser, without needing to compile for each OS. You just need to target Docker and the browser.

Like what I have to say? Consider subscribing to my newsletter. I don't send very many updates.