dockerrustcompilationmusldistroless

static linking for rust without glibc - scratch image


I am following the example dockerfile of From Zero to Production

It looks like this to me:

FROM lukemathwalker/cargo-chef:0.1.67-rust-1.78.0-slim-bookworm as chef
WORKDIR /app
RUN apt update && apt install lld clang -y

FROM chef as planner
COPY . .
RUN cargo chef prepare  --recipe-path recipe.json

FROM chef as builder
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
COPY . .
ENV SQLX_OFFLINE true
RUN cargo build --release --bin zero2prod

FROM debian:bookworm-slim AS runtime
WORKDIR /app
RUN apt-get update -y \
    && apt-get install -y --no-install-recommends openssl ca-certificates \
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/zero2prod zero2prod
COPY configuration configuration
ENV APP_ENVIRONMENT production
ENTRYPOINT ["./zero2prod"]

however, I want to make the image smaller by using a scratch container.

Notice, I am on an ARM (M2) Macbook and want to create docker containers with linux amd64 and aarm64 architecture

The following works:

FROM lukemathwalker/cargo-chef:0.1.67-rust-1.78.0-slim-bookworm as chef
WORKDIR /app
RUN apt update && apt install lld clang -y

FROM chef as planner
COPY . .
RUN cargo chef prepare  --recipe-path recipe.json

FROM chef as builder
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
COPY . .
ENV SQLX_OFFLINE true

RUN cargo build --release --bin zero2prod

# Collect dependencies for zero2prod
RUN mkdir /dependencies && \
    ldd /app/target/release/zero2prod | tr -s '[:blank:]' '\n' | grep '^/' | xargs -I '{}' cp --parents '{}' /dependencies



FROM scratch AS runtime
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /dependencies /
COPY --from=builder /app/target/release/zero2prod /zero2prod
COPY configuration /configuration
ENV APP_ENVIRONMENT production
ENV RUST_LOG="error,$BINARY_NAME=info"
ENTRYPOINT ["/zero2prod"]

by executing

docker buildx build --tag zero2prod:latest --target runtime --file Dockerfile .
docker run --rm -p 8000:8000 zero2prod:latest

but is not creating a single (statically) linked binary.

How can I create such a statically linked build? Such that

FROM scratch AS runtime
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

COPY --from=builder /app/target/release/zero2prod /zero2prod
COPY configuration /configuration
ENV APP_ENVIRONMENT production
ENV RUST_LOG="error,$BINARY_NAME=info"
ENTRYPOINT ["/zero2prod"]

works?

If I understand the situation correctly I need to look into MUSL & cross-build - but am still a bit confused -especially as trying to enable static linking fails for me with:

#ENV RUSTFLAGS='-C target-feature=+crt-static'
#0.176 error: cannot produce proc-macro for `actix-macros v0.2.4` as the target `aarch64-unknown-linux-gnu` does not support these crate types

This is also the case for other compiler options and targets that I have explored so far

#ENV CC=musl-gcc \
#    CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc
    #RUSTFLAGS='-C target-feature=-crt-static'

#ENV CC_aarch64_unknown_linux_musl=aarch64-linux-musl-gcc \
#    CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-gcc \
#    RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-lgcc"
# Add the target for musl
#RUN rustup target add x86_64-unknown-linux-musl
# RUN cargo build --release --bin zero2prod --target x86_64-unknown-linux-gnu

Even distroless works:

FROM lukemathwalker/cargo-chef:0.1.67-rust-1.78.0-slim-bookworm as chef
WORKDIR /app
RUN apt update && apt install lld clang -y

FROM chef as planner
COPY . .
RUN cargo chef prepare  --recipe-path recipe.json

FROM chef as builder
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
COPY . .
ENV SQLX_OFFLINE true

RUN cargo build --frozen --release --bin zero2prod

FROM gcr.io/distroless/cc-debian12 AS runtime
USER 1000
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/target/release/zero2prod /zero2prod
COPY configuration /configuration
ENV APP_ENVIRONMENT production
ENV RUST_LOG="error,$BINARY_NAME=info"
ENTRYPOINT ["/zero2prod"]

but how can I make FROM scratch AS runtime build as well?

edit 2

I know I need to add musl-tools

and also:

RUN rustup target add x86_64-unknown-linux-musl
RUN rustup target add aarch64-unknown-linux-musl

and modify

RUN cargo chef cook --release --target aarch64-unknown-linux-musl --recipe-path recipe.json

and also modify

RUN cargo build --frozen --release --target aarch64-unknown-linux-musl --bin zero2prod

to add the target.

I do not receive any error this way - however, the zero2prod binary is never built in the folder.


Solution

  • this is the important line line that was missing

    COPY --from=builder /app/target/aarch64-unknown-linux-musl/release/zero2prod /zero2prod
    

    I was using

    COPY --from=builder /app/target/release/zero2prod /zero2prod
    

    See https://users.rust-lang.org/t/static-linking-for-rust-without-glibc-scratch-image/112279/5 for a much refined answer and example https://github.com/marvin-hansen/mimalloc

    instead