Stripping Cargo project for Docker

Hi,

Is there an easy way to build a Rust executable "inside Dockerfile"? To install "bare bones" toolchain and do it without being too much of an expert?

You can either curl (rustup url) | shell as on any "new" machine, or you can start with a rust image.

Since you say "stripping" in the subject, maybe you want the resulting docker image to be small? One way of doing that is by building in one image, and then copying the binary to another image.

Here's an old Dockerfile where I do that:

FROM rust:1.46 as builder
WORKDIR /usr/src/kpm

# First build dependecies only, so those artifacts can be cached in image.
COPY Cargo.toml Cargo.lock ./
RUN mkdir -p src; \
    echo 'fn main() {}' > src/main.rs && \
    cargo build --release && \
    rm -rf src

# Then build the app
COPY src src
# The touch command in this script is to make sure the app itself is
# actually rebuilt after the dependency build with an empty main.
RUN \
    touch src/main.rs && \
    cargo install --path .

# Then start a new slim image (without dev tools) and copy in the binary
FROM debian:buster-slim

# Install any extra packages needed at runtime
RUN apt-get update && \
    apt-get install -y libssl1.1 ca-certificates && \
    rm -rf /var/lib/apt/lists/*

COPY --from=builder /usr/local/cargo/bin/kpm /usr/local/bin/kpm

# Some other files may be needed at runtime, ifs so, copy them into the result image.
COPY templates templates
CMD ["kpm"]

Thanks! I'm aware of the two-stage building trick. Besides, I can't decide which is the best way to build two currently independent projects in different git repositories as one Docker project. One is a Cargo project, another is a Poetry project. My image is now python slim. In theory, one can merge them into one project, because the files does not seem to conflict, but it's an ugly mouthful just the same :slight_smile: . Any suggestions?

While you need the python installation to execute your python code, you don't need the rust toolchain at all to execute the binary produced. It's pure waste to include rustc and cargo to the production container image.

Let me explain the current state of affairs. My Rust executable is built on a VBox Debian Guest and is copied manually to the root of the Poetry project, to be referred to in its Dockerfile. Very economical, yet not quite right. So far I can't see the right way to manage it.

Are both the python and the rust program independant server process? If so it might be better to make them as separate images and manage them from the higher abstractions like k8s pods or just a bash scripts which runs two containers. Besides that, I don't think it's not right to put the executable wherever you want.

No, it's small fry. Just a couple of utilities to cohabit one image naturally. If my current approach is adequate, so be it. It's just that one-click build of everything looks better yet :slight_smile:

What about first using FROM rust:<rust version> as builder and build the rust program and then FROM python:<python version>-slim and finally copy the compiled rust executable?

My Python Dockerfile is already two-stage. Poetry is heavy enough to be factored out to the first stage. Is it possible to make three stages, passing from 1 to 2 just a Rust executable, and from 2 to 3 both Rust executable and Python sdist?

Ah, yes, another problem: Cargo and Poetry projects do not live in the same directory :slight_smile:

Not-so-obvious solution is to use git clone :blush:

FROM rust:1.57.0-slim-bullseye AS cargobase
...
RUN git clone <the rust project>
...
RUN cargo build --release
...
FROM python:3.10.1-slim-bullseye AS poetrybase
...
FROM python:3.10.1-slim-bullseye
...
COPY --from=poetrybase /home/$user/$project/dist/ ./dist/
RUN pip install ./dist/* --user
...
COPY --from=cargobase /home/$cuser/$cproject/target/release/$cproject .local/bin
...

The resulting image is 200MB, the biggest intermediate is 1.09GB.