DEV Community

Cover image for Use Multi-Stage Docker Builds For Statically-Linked Rust Binaries

Use Multi-Stage Docker Builds For Statically-Linked Rust Binaries

Ben Lovy on January 29, 2020

I'm making a static website in Rust. Last time I did this, I used Docker to automate the deployment. I was frustrated at how much bandwidth I was...
Collapse
 
wincent profile image
Wincent

Nice guide!

Have been looking for a working example of Rust multistage builds.
My rocket web app had an image size of 2.45GB which is now reduced to 9.19MB.

Update for Rust 2021,
In the build stage, replace this line:
RUN cargo build --release

With this:
RUN cargo install --target x86_64-unknown-linux-musl --path .

Collapse
 
deciduously profile image
Ben Lovy

Thank you!

Collapse
 
uminer profile image
Moshe Uminer

I've used staged builds before, but never used scratch for the last stage, only alpine. I wonder if there are tradeoffs to not using a Linux environment?

Collapse
 
deciduously profile image
Ben Lovy

I'm curious to see if it holds me up when I inevitably do have other assets to include. When the only thing in the container is a single binary it's straightforward enough, at least.

All other concerns should be handled by the host server, though, I think - right?

Collapse
 
bretthancox profile image
bretthancox • Edited

Appreciate this article so much. So many concepts being added to my knowledge-base for future use.

One thing I find is that I'm managing container-container communications a lot. Not a problem normally. How is that with scratch? Does Docker handle it all, or does the OS provide some abstraction that would need to be substituted within the binary?

EDIT: To be clear, I'm thinking of putting a binary at the end of an API call. If I could do that with a minimal image I would be so much happier. Just want to get the plumbing right 😉

Thread Thread
 
deciduously profile image
Ben Lovy

I haven't tried yet, but my instinct tells me this is something Docker manages, not your containers. Like Moshe said, the Linux userland inside a container is explicitly for runtime needs of the container's internal commands, everything outside of that is handled by Docker itself.

Thread Thread
 
bretthancox profile image
bretthancox

Thanks. scratch feels like taking the training wheels off, but you have to do it at some point!

Thread Thread
 
qm3ster profile image
Mihail Malo

Afaik, scratch isn't "nothing", it just downloads nothing.
There's still stuff from the runtime when it's alive.

Collapse
 
uminer profile image
Moshe Uminer

Yeah, I never thought about it much, but the OS in the docker image is really only needed for runtime environment purposes. If all you have is a binary, you shouldn't need an environment aside from the host.

The scratch image description actually says the following:

This image is most useful in the context of building base images (such as debian and busybox) or super minimal images (that contain only a single binary and whatever it requires, such as hello-world).

Collapse
 
jeikabu profile image
jeikabu

Very timely for me. I've started using docker to wrangle some of the more complicated toolchains like cross-compiling (not unlike Rust+musl, really). And specifically multi-stage docker where I was using multiple Dockerfiles and had scripts "gluing" the results together.

Collapse
 
deciduously profile image
Ben Lovy

cross-compiling

Definitely interested in this. I've always used Gentoo/QEMU or Nix tooling to handle this sort of thing and never got to a point where it didn't feel clunky and brittle (gluing together stuff I don't understand). Docker sounds nice and clean.

Collapse
 
jeikabu profile image
jeikabu

I wrote about using docker and Qemu a while back which is pretty slick. But I’ve run into a few issues; dotnet core doesn’t work in Qemu, and some more ”obscure” platforms like ESP32 basically require cross-compiling since they lack native toolchain support.

Think I remember seeing another approach to dealing with dependencies and the docker cache, will have to try and find it. I know the bare minimum docker but really need to up my game.

Collapse
 
gypsydave5 profile image
David Wickes • Edited

I've used scratch builds for some of the Go stuff I do. It definitely makes you feel a bit smug when your image is only a few MB.

Good work!

Collapse
 
deciduously profile image
Ben Lovy

Anytime you casually shave off an order of magnitude or two is cause for celebration, especially when it's a relatively trivial change.

Collapse
 
deepitstl profile image
Luke

Debugging can be tricky, but not impossible, especially with Kubernetes deployment, which tends to require privileged rights.

Collapse
 
deciduously profile image
Ben Lovy

Good to keep in mind, thanks! Multi-stage builds in general or specifically FROM scratch?

Collapse
 
deepitstl profile image
Luke

FROM scratch specifically. See ahmet.im/blog/debugging-scratch/ for more details.

Collapse
 
coreyja profile image
Corey Alexander

Wow I also learned about scratch here that's really cool! I'll have to try that on an image or two I have!

Collapse
 
qm3ster profile image
Mihail Malo

Why have an executable for a static site?
Is it not static, just server-rendered with dynamic data?

Collapse
 
deciduously profile image
Ben Lovy

Mostly for educational purposes, but yes, it's more accurately server-rendered with potentially dynamic data. Right now nothing is dynamic, but I'm looking at this project as a sort of "playground", and this doesn't close any doors should I want to do something fancy in the future.

Collapse
 
qm3ster profile image
Mihail Malo

Ah, makes sense.
I just don't see a lot of advantages to server "rendering" vs an API.
The only thing that comes to mind is reduced complexity for very small projects.
But education is education!

Collapse
 
imbolc profile image
Imbolc

What the purpose of the container if all you need is just a single binary?