Rust for Docker - tiny containers FTW
I discover that just like Go, Rust pairs greatly with Docker
Published 27. February 2024
If you are any bit familiar with k8s you may know a little trick for making tiny images: the scratch "image". It's not really a Docker image as much as it indicates to not use any image as a base when building, meaning you start from scratch (as the name implies).
Using scratch is attractive. Base images are a huge waste of space. Why bundle an entire OS's worth of support libraries just to run your application? You could of course optimize the image by deleting unused packages (is there a docker image tree shaker?), but that's a lot of work. Why do you think so many people still use debian
, or even worse, ubuntu
as their base?
I've heard about the use of scatch
in the context of developing microservices written in go
, but not Rust. Having dabbled in Rust lately, I wanted to see if I could build docker containers from scratch for Rust as well.
Searching the internet, people seem confused about it. There's an abandoned GitHub project aiming to provide this to people through the use of a specific build container: https://github.com/emk/rust-musl-builder.
Brenden Hyde has also had a go at this problem in 2020: https://bxbrenden.github.io/. He uses the aforementioned github repo, which seems to have still been maintained at that time. Sadly, by the time I tried to replicate his success in 2024, things were falling apart.
It's not that hard: This is all you needâ„¢
Having figured out an easy way of doing it in 2024, it's my turn to carry the banner, sharing this useful recipe that has been useful during my Rust learning journey.
This is based on https://stackoverflow.com/questions/40695010/how-to-compile-a-static-musl-binary-of-a-rust-project-with-native-dependencies, which gives a valid and elegant solution:
The commands
rustup target add x86_64-unknown-linux-musl
cargo build --release --target=x86_64-unknown-linux-musl
Wow. So simple. I love it!
Pairing it with Docker
This is where the magic happens: By using a multi stage docker container, we can install all the dependencies we want and build the container, then copy it to a scratch
image when we are done. Endless possibilities!
FROM rust:latest
COPY . .
RUN rustup target add x86_64-unknown-linux-musl
RUN cargo build --release --target=x86_64-unknown-linux-musl
FROM scratch
COPY --from=0 target/x86_64-unknown-linux-musl/release/appname /appname
# You can copy other files you need as well. Config, assets, anything really.
COPY --from=0 assets /
CMD ["/appname"]
The result: a tiny docker image that lets you run your application basically anywhere cloud-related.
Bonus: Speed up builds in CI by building the rust executable outside docker so you can use your CI platforms cache system to your advantage. Then, copy the finished program in, and you are good to go!