Recently I was writing a Rust project using reqwest
to call an API or two and I ran into an issue…
When I decided to deploy the small project into a Container and run it as a small service but I couldn’t compile the code.
But why?
It turns out its because of two reasons:
- I was using an Alpine image (a minimal Linux distribution) which uses musl libc (not glibc).
- OpenSSL issues because
reqwest
uses it for TLS via OpenSSL.
Here is a sample image to resolve the issues.
Alpine Image
Here is the image and the break down.
Container Image:
FROM docker.io/library/rust:1.80-alpine as build
WORKDIR /app
COPY . .
RUN apk add --no-cache pkgconf alpine-sdk openssl-dev perl musl-dev && \
rustup target add x86_64-unknown-linux-musl && \
cargo build --release --target x86_64-unknown-linux-musl
FROM docker.io/library/alpine:3.20
WORKDIR /app
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/rust-alpine-openssl /app/rust-alpine-openssl
ENTRYPOINT [ "/app/target/release/rust-alpine-openssl" ]
Breakdown
- Create base image as a build container
FROM docker.io/library/rust:1.80-alpine as build
This first step allows us to create an image with all the tools / libraries we need to compile the code but not have all the build-time dependencies in our run-time container.
- Create working directory + copy the code into our container
WORKDIR /app
COPY . .
Note: You may want a .dockerignore
file to help not copy everything into the container.
- Install deps and build the project
Install all the build-time dependencies
apk add --no-cache pkgconf alpine-sdk openssl-dev perl musl-dev
The first part of the run command installs using apk
(Alpine’s package manager) all the build tools we’ll need for our project.
This includes openssl-dev
(OpenSSL) and other tools such as pkgconf
(pkgconf), alpine-sdk
(Alpine SDK), perl
(Perl), and musl-dev
(musl libc).
Install Rust target:
rustup target add x86_64-unknown-linux-musl
This installs the musl target using rustup so the Rust toolchain can produce a musl-linked binary.
Compile / Build the project
Last step on the build container is to compile a release artifact of the Rust project with Cargo, making sure to specify the target (from previous step).
- Define our final image
FROM docker.io/library/alpine:3.20
We define a new vanila alpine v3.20 image withour Rust and the other tools / libraries. This is because we don’t need these dynamic libraries at runtime as they are statically compiled into our binary.
- Create and sets working directory
WORKDIR /app
This creates and sets the working directory for our final image.
- Copy the compiled binary from the build container
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/rust-alpine-openssl /app/rust-alpine-openssl
This copies the compiled binary from the build container into our final image.
- Define the entrypoint
ENTRYPOINT [ "/app/target/release/rust-alpine-openssl" ]
This defines the entrypoint for our container to run the compiled binary.
Conclusion
And there you have it, a small Rust project compiled and running in an Alpine container using OpenSSL. This is a great way to create small, efficient containers for your Rust projects that use OpenSSL. Happy Coding!