Thursday, June 11, 2020

How to cross compile Rust on MacOs and target Linux using Docker container

I use a Mac for development, and recently I had to compile a Rust project to be run on a Linux server. This post captures how I managed to get this done. Note that the method highlighted in this post can also be used if you have a Windows (or even a Linux) dev environment.

The first thing I attempted was to install the necessary toolchain that should, in theory, allow me to cross-compile the Rust project on my Mac and target Linux. This did not go as smoothly as I would have loved. The first issue I encountered was with OpenSSL. After fixing that, the next issue was with backtrace-sys with the build failing with `error: failed to run custom build command for backtrace-sys v0.1.34`

This looks like I probably do not have the right environment/toolchain/dependencies installed.

I had the option of trying to identify all the required dependencies, but I was really running out of time and frankly, hunting around for the correct dependencies needed to set up a working compiler toolchain is also not fun. So I ended up making use of Docker to help with the build processes.

What I did was to create an image based on alpine Linux with the required toolchain. I then use the container spurn based on the image for the build processes. The procedure is as follows:

The Dockerfile:

FROM alpine:3.11 AS build
LABEL maintainer="Dadepo Aderemi"

#
# -- Install Rust build toolchain
#
ENV RUSTFLAGS="-C target-feature=+crt-static"
RUN apk add rust cargo openssl-dev

The +crt-static ensures statically linking of runtime dependencies. For more on this see Static and dynamic C runtimes

Now make sure to be in the same directory the docker file is located and create the image by running:

docker build -t rust-linux-builder .

Once built, run and connect into the container with the source code of the project mounted as volume by running:

docker run -it --rm --net=host -v $(pwd):/build rust-linux-builder

Note using $(pwd) assumes your current directory is where the source of the project is located. Also the use of the --rm flag ensures the container is removed automatically on exit. No need to keep the container around after finishing with the build.

Once in the container, switch to /build then run the build command:

/ # cd /build/
/build # cargo build --target x86_64-alpine-linux-musl --release

Once the build completes, exit the container and the binary would be found within the /target directory. Which can then be copied and installed on the linux server where it would be executed.

As you can see, there is nothing complicated in the Dockerfile and you can easily create your own, with your modification if need be. But in case you do not want to do that, I pushed the Docker image to Docker hub. This means you can easily get your rust project, targeting Linux by running:

docker run -it --rm --net=host -v $(pwd):/build dadepo/rust-linux-builder

...and follow the steps outlined above.

1 comment:

Anonymous said...

Hi

I'm just learning docker so your post was very useful to me. Thanks.

One small improvement, I think, is to add the following to the Dockerfile:

WORKDIR /build

That way you don't have to "cd /build" before running cargo. This also makes it possible to run it directly like this:

$ docker run --rm --net=host -v $(pwd):/build rust-linux-builder cargo build
Compiling hello v0.1.0 (/build)
Finished dev [unoptimized + debuginfo] target(s) in 2.52s