Multiarch Docker Containers with Rust

3 min read 411 words

This guide shows how to build multiarch Docker containers for Rust applications using cargo zigbuild for cross-compilation and Docker build commands for architecture-specific or multiarch container images. Based on the example for Building a Rust API with Rocket and JWT Authentication, this guide provides the next steps.

Cross-Compiling with Zigbuild

Install cross-compilation targets for ARM64 and x86_64:

# Install rustup targets for arm and x86
$ > rustup target add aarch64-unknown-linux-gnu
$ > rustup target add x86_64-unknown-linux-gnu

Build release binaries for both architectures using cargo zigbuild:

# Build architecture binaries for linux
$ > cargo zigbuild --release --target x86_64-unknown-linux-gnu
$ > cargo zigbuild --release --target aarch64-unknown-linux-gnu

The zigbuild command handles cross-compilation toolchain setup automatically. Binaries are created in target/x86_64-unknown-linux-gnu/release/ and target/aarch64-unknown-linux-gnu/release/ respectively.

Dockerfile for Multiarch Builds

Use a single Dockerfile that selects the appropriate pre-built binary based on the target architecture:

FROM amazonlinux:2023 AS base

ARG TARGETARCH

RUN --mount=type=bind,source=./target/aarch64-unknown-linux-gnu/release,target=/mnt/arm \
    --mount=type=bind,source=./target/x86_64-unknown-linux-gnu/release,target=/mnt/x86 \
    if [ "$TARGETARCH" = "arm64" ] && [ -f /mnt/arm/rocket-example ]; then \
      cp /mnt/arm/rocket-example /usr/local/bin/rocket-example; \
    elif [ "$TARGETARCH" != "arm64" ] && [ -f /mnt/x86/rocket-example ]; then \
      cp /mnt/x86/rocket-example /usr/local/bin/rocket-example; \
    else \
      echo "Error: No service binary found for architecture!" >&2; \
      exit 1; \
    fi && \
    chmod +x /usr/local/bin/rocket-example

# Default version
FROM base AS default

ENV ROCKET_LOG_LEVEL=debug
ENV ROCKET_ADDRESS=0.0.0.0
ENV ROCKET_PORT=3000

CMD ["rocket-example"]

The Dockerfile uses Docker’s ARG TARGETARCH to detect the target architecture at build time. Bind mounts access the pre-built binaries from the local target/ directory and copy the appropriate binary based on the architecture. This ensures each container only contains the binary for its architecture, avoiding bloated container sizes.

Building Architecture-Specific Containers

Build containers for specific architectures using the --platform flag:

# Build Docker for ARM64
$ > docker build \
    --platform linux/arm64 \
    -f Dockerfile . \
    -t rocket-example:latest-arm64
# Build Docker for x86_64
$ > docker build \
    --platform linux/amd64 \
    -f Dockerfile . \
    -t rocket-example:latest-amd64

Using architecture-specific tags like latest-arm64 and latest-amd64 ensures each container only contains the binary for its architecture, avoiding bloated container sizes.

Building Multiarch Containers with Buildx

Build for multiple architectures in a single command using Docker Buildx:

$ > docker buildx build --platform linux/amd64,linux/arm64 . \
    -t rocket-example:latest

This creates a generic latest tag with a manifest list that includes both architectures. Docker automatically selects the correct architecture when pulling or running the container.