LukeMathWalker / zero-to-production

Code for "Zero To Production In Rust", a book on API development using Rust.
https://www.zero2prod.com
Apache License 2.0
5.73k stars 491 forks source link

reduce container size with scratch & static linking #261

Closed geoHeil closed 4 months ago

geoHeil commented 4 months ago

I am following the example dockerfile of https://github.com/LukeMathWalker/zero-to-production/blob/root-chapter-05/Dockerfile

It looks like this for 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 a 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 could explore 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

edit

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?

LukeMathWalker commented 4 months ago

This is beyond the scope of the book—I invite you to post this issue on other Rust community forums.
This type of issue is one of the reasons we don't build using scratch as the base image in the book.