Notes on building and running Rust binaries in Docker.
Image information for the Rust hello world1 program.
Base | Size | Libc | CRT Linkage |
---|---|---|---|
debian | 124MB | glibc | dynamic |
debian:slim | 80.8MB | glibc | dynamic |
distroless:cc | 23MB | glibc | dynamic |
alpine | 7.47MB | musl | static |
distroless:static | 3.63MB | glibc | static |
scratch | 1.29MB | glibc | static |
scratch | 416KB | musl | static |
Base | Size | Libc | CRT Linkage | TLS | DNS |
---|---|---|---|---|---|
scratch | 4.49MB | glibc | static | rustls | TrustDNS |
Alpine provides the smallest OS while keeping just about everything a Rust program will need to function.
The Rust official Docker support Alpine for building Rust programs through the :alpine
tag.
It's important to note that Alpine uses musl libc instead of glibc, and as such you may encounter issues with other libraries expecting the glibc implementaiton, and potentially servere performance differences. The Rust std defaults to using the system allocator, in this instance the malloc implementation provided by musl, however it can be replaced with another allocator by using the global_allocator attribute.
Debian provides a stable base from which to run any Rust program, and is the default used by the Rust official Docker images.
While providing the most stable and predictable environment, Debian also includes many features, programs, and libraries which are unnecessary for most workloads.
Using the debian:slim
tag will shave 40MB off, taking it from 120MB to 80MB, though you'll need to reinstall ca-certificates
for typical networked applications.
Distroless is an initiative to provide containers with only what is needed to run a given application.
There are a number of flavours provided, however cc and static are most relevent for Rust, and are very slim, coming in at 22MB and 3MB respectively.
Scratch, while not technically an image, does provide the means to achieve the smallest image sizes.
Because scratch includes nothing, you will need to provide everything your application needs to operate, including libc, ssl libraries, certificates, etc.
Rustls is a Rust based TLS implementation leveraging ring for cryptography and webpki for TLS certificate validation. It has good support for recent TLS features and intentionally does not provide support for outdated or insecure uses.
Using rustls allows us to avoid a dependency on OpenSSL, which is usually provided by the OS.
Rust "the language" has no dependency on libc (or any c runtime), however, the Rust standard library (std
) does2 to provide a convenient and stable way to interact with the host system.
Because Rust programs usually want to use the std, and libc is usually provided by the OS, the base image is an important factor for containerised Rust.
While glibc is the "default" libc, musl is also supported.
Care needs to be taken when choosing to use musl as other linked libraries may not be expecting it, it can have surprising performance implications, and it has relatively less development resources.
The Rust standard library supports both static and dynamic linking of the C runtime (CRT). Generally it will be dynamically linked, except for targets specifying musl.
If you're looking to reduce the size of your images and you've already optimised the base image, reducing the size of your Rust binary is a good next step.
There are many factors that affect binary size in Rust, I recommend you start with min-sized-rust.
There are also experimental ways to write rust code with the convenience of std without needing libc/C code, such as rustix + mustang or relibc.