Building and Running a Rust Application with Docker on Mac (Apple Silicon)
JohnDotOwl
Posted on March 13, 2024
This guide will walk you through the process of building and running a Rust application using Docker. The provided Dockerfile is designed to build a Rust application for the ARM64 architecture and run it in a lightweight Debian container.
Dockerfile
# syntax=docker/dockerfile:1
################################################################################
# Create a stage for building the application.
ARG RUST_VERSION=1.69.0
ARG APP_NAME=my_app_name
FROM --platform=linux/arm64 rust:${RUST_VERSION}-slim-bullseye AS build
ARG APP_NAME
WORKDIR /app
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y pkg-config openssl libssl-dev ca-certificates
RUN \
apt-get install ca-certificates && \
apt-get clean
# Build the application.
# Leverage a cache mount to /usr/local/cargo/registry/
# for downloaded dependencies and a cache mount to /app/target/ for
# compiled dependencies which will speed up subsequent builds.
# Leverage a bind mount to the src directory to avoid having to copy the
# source code into the container. Once built, copy the executable to an
# output directory before the cache mounted /app/target is unmounted.
RUN --mount=type=bind,source=src,target=src \
--mount=type=bind,source=Cargo.toml,target=Cargo.toml \
--mount=type=bind,source=Cargo.lock,target=Cargo.lock \
--mount=type=cache,target=/app/target/ \
--mount=type=cache,target=/usr/local/cargo/registry/ \
<<EOF
set -e
rustup target add aarch64-unknown-linux-gnu
cargo build --locked --release --target aarch64-unknown-linux-gnu
cp ./target/aarch64-unknown-linux-gnu/release/$APP_NAME /bin/server
EOF
################################################################################
# Create a new stage for running the application that contains the minimal
# runtime dependencies for the application. This often uses a different base
# image from the build stage where the necessary files are copied from the build
# stage.
#
# The example below uses the debian bullseye image as the foundation for running the app.
# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the
# most recent version of that tag when you build your Dockerfile. If
# reproducibility is important, consider using a digest
# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57).
FROM --platform=linux/arm64 debian:bullseye-slim AS final
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser
# Copy the executable from the "build" stage.
COPY --from=build /bin/server /bin/
# Expose the port that the application listens on.
EXPOSE 1000
# What the container should run when it is started.
CMD ["/bin/server"]
Understanding the Dockerfile
The Dockerfile consists of two stages: a build stage and a final stage.
Build Stage
The build stage is responsible for compiling the Rust application. It uses the official Rust Docker image as a base and installs the necessary dependencies. The Dockerfile then leverages several Docker features to optimize the build process:
Bind Mounts: The source code is mounted into the container using a bind mount, avoiding the need to copy the code into the container for each build.
Cache Mounts: The Cargo registry and compiled dependencies are cached using cache mounts, speeding up subsequent builds by reusing downloaded and compiled artifacts.
The build stage compiles the Rust application using cargo build --release
and copies the resulting binary to /bin/server
.
Final Stage
The final stage creates a minimal runtime environment for the application using the debian:bullseye-slim
image as a base. It performs the following steps:
Create a Non-Privileged User: A non-privileged user named appuser is created to run the application, following best practices for container security.
Copy the Binary: The compiled binary from the build stage is copied into the final image at /bin/server.
Expose Port: The container exposes port 1000, which is the port the application listens on.
Default Command: The container is configured to run the /bin/server binary when started.
Building the Docker Image
To build the Docker image, run the following command in the directory containing the Dockerfile:
docker build --platform linux/arm64 -t my_app_name .
Running the Docker Container
To run the Docker container, use the following command:
docker run -d --name my_app_name -p 1000:1000 --restart on-failure --memory 1g --memory-swap 1g my_app_name
Posted on March 13, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.