Building Minimal Docker Containers for Rust Applications
Inspired by Nick Gauthier's work on building minimal Go containers and Erik Kidd's work on simple compilation of Rust binaries with no dependencies, I was curious to see how small a comparative Rust container serving HTTP via the Iron framework could be.
In this article, I will be combining an example 'Hello World' HTTP server with rust-musl-builder
and a bare-bones Docker container to create a tiny self-contained Docker image and compare it to the minimal Docker containers built in Go.
A Simple 'Hello World' Server in Rust
The example code for the 'Hello World' webserver comes straight from the Iron framework starting page, with the http
command changed to listen on all network interfaces:
extern crate iron;
use iron::prelude::*;
use iron::status;
fn main() {
fn hello_world(_: &mut Request) -> IronResult<Response> {
Ok(Response::with((status::Ok, "Hello World!")))
}
println!("On 3000");
Iron::new(hello_world).http("0.0.0.0:3000").unwrap();
}
with the iron
dependency added to Cargo.toml
:
[dependencies.iron]
version = "*"
Using a local rust installation, we can verify that the webserver works:
$ cargo run
[...]
Compiling myapp v0.1.0 (file:///path/to/myapp)
Running `target/debug/myapp`
On 3000
and in another tab, that it is responding correctly:
$ curl localhost:3000
Hello World!
Compiling Statically
Thanks to Erik Kidd's awesome rust-musl-builder, creating static Rust binaries without dependencies is a breeze. rust-musl-builder
packages a Rust compiler with the minimal musl C standard library into a Docker container so that statically linked Rust binaries can be created without installing a custom compiler on your computer. Instead, a simple shell alias for running builds in the container is provided that will pull the necessary Docker image on the first invocation:
$ alias rust-musl-builder='docker run --rm -it -v "$(pwd)":/home/rust/src ekidd/rust-musl-builder'
$ rust-musl-builder cargo build --release
Since the current working directory is mapped into the compilation container, the compiled binary will be found in the target/x86_64-unknown-linux-musl/release
folder. We can verify that it is indeed statically compiled - and tiny:
$ ldd target/x86_64-unknown-linux-musl/release/myapp
not a dynamic executable
$ ls -lh target/x86_64-unknown-linux-musl/release/myapp
-rwxr-xr-x 1 seemayer seemayer 1.9M Jul 13 18:53 target/x86_64-unknown-linux-musl/release/myapp
We now have a 1.9 MB HTTP server that responds 'Hello World' to requests on port 3000 without needing any external dependencies.
Building a Minimal Docker Container
Since the binary does not contain dependencies, it will work fine when running in a Docker container that was built from scratch (i.e. not based on an existing image). The following Dockerfile will be enough to create a bare-bones container for running our app:
FROM scratch
ADD target/x86_64-unknown-linux-musl/release/myapp /
EXPOSE 3000
CMD ["/myapp"]
With the Dockerfile, building and running the app is easy and the from-scratch Docker image is virtually the same size as the binary we compiled:
$ docker build -t myapp .
[...]
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp latest b7d042250a69 About an hour ago 1.929 MB
Now the big moment of truth! We can use docker run
to run our newly built image, binding port 3000 on the host to port 3000 in the container:
$ docker run --rm --name myapp -p 3000:3000 myapp
On 3000
The console message tells us that the binary is running successfully. In another terminal, we can confirm that the container is responding to requests:
$ curl localhost:3000
Hello World!
Conclusion
As the Rust ecosystem for web development matures, running bare-bone Rust services in Docker containers is a super-low-overhead and high-performance way of deploying webapps. Compared to the 6.1 MB Go container created by Nick Gauthier or the 3.6 MB Go container created by Adriaan de Jonge, our 1.9 MB Rust container is almost half as big as the smallest Go container and was easy to create.
I'm excited for the future of Rust web development!