Dockerise Rust Actix App
Learn how to containerize a Rust Actix application using Docker with a focus on creating lightweight Docker images using distro-less techniques.
9 janvier 2024
Published
Hugo Mufraggi
Author

Dockerise Rust Actix App
I present this new article on the Rust architecture DevOps/telemetry arc, following the article on software architecture. To integrate telemetry into our backend, we must first containerize our project, run it in a composed Docker, and implement telemetry.
You can find the articles from the previous arc on Medium and the code on my GitHub.
Dockerfile
The Dockerfile will allow us to create an image of our backend that can be used in a Docker-compose or later in Kubernetes. Consider this image as a snapshot of the project with the ability to run it wherever Docker is installed, facilitating deployments and project versioning.
The goal is to have the lightest possible Docker container.
In the past, Alpine bases were commonly used to save space. Currently, this is no longer considered a best practice. Alpine images had security flaws and were poorly maintained. Then came distro-less images.
Distro-less
You can find a video from 2017 introducing the concept on Google's GitHub.
The advantages are numerous, and I will list them for you.
Distro-less images include only the application and runtime dependencies — no package manager, no bash, and no other programs. A best practice used by Google and other major players allows risk reduction. Ultra-lightweight gcr.io/distroless/static-debian11 weighs approximately 2 MiB, compared to Alpine (~5 MiB) and Debian (124 MiB). I encourage you to check Google’s GitHub for various statistics and explanations from the team in the README.
Dockerfile Multi-Stage
If I’ve simplified what a distro-less image is, you understand that we can’t use it as it is. We’ll divide the creation of our Docker image into two stages.
Build Stage
The build stage compiles our Rust project.
⚠️ Distro-less images can also be used for your frontend projects.
FROM rust:1.63.0 as build
ARG DATABASE_URL
WORKDIR /usr/src/api-service
COPY . .
ENV DATABASE_URL=$DATABASE_URL SQLX_OFFLINE=true RUST_BACKTRACE=1 PKG_CONFIG_ALLOW_CROSS=1
EXPOSE 8080
RUN cargo install --path .
I chose to start from the rust:1.63.0 image. The build tag defines that this part is the build stage. I’ve chosen to pass the database URL as an ARG for later convenience. Then, I link it to an ENV variable for the database URL.
This part is quite standard, and we end with a RUN cargo install --path . that compiles our Rust project.
Package Stage
FROM gcr.io/distroless/cc-debian10
COPY --from=build /usr/local/cargo/bin/hexa-domain-tutorial /usr/local/bin/hexa-domain-tutorial
ENTRYPOINT ["/usr/local/bin/hexa-domain-tutorial"]
Finally, the distro-less image: FROM gcr.io/distroless/cc-debian10. We copy the build done in the build stage, allowing us to store only the binary of our application. Both stages are combined in a single Dockerfile.
FROM rust:1.63.0 as build
ENV PKG_CONFIG_ALLOW_CROSS=1
ARG DATABASE_URL
WORKDIR /usr/src/api-service
COPY . .
ENV DATABASE_URL=$DATABASE_URL SQLX_OFFLINE=true RUST_BACKTRACE=1
EXPOSE 8080
RUN cargo install --path .
FROM gcr.io/distroless/cc-debian10
COPY --from=build /usr/local/cargo/bin/hexa-domain-tutorial /usr/local/bin/hexa-domain-tutorial
ENTRYPOINT ["/usr/local/bin/hexa-domain-tutorial"]
From the beginning, we’ve been discussing optimizing the size of our Docker container. Once the image is built and the container is launched, it is only 31 MB. Mission accomplished 🔥
We will stop here for this article. You can launch your Docker image locally, but you may need to deal with networks or wait for the next article where we will integrate it into a Docker-compose with a PostgreSQL database.