How I release super tiny busybox containers of my rust app


#1

I’ve been playing megabyte golf with my carbon/whisper time series database daemon and I’ve gotten it down to 2MB (4.27MB when expanded) for a fully-functioning UDP/TCP writer. I thought it’d be fun to talk about it a bit and get people’s feedback.

This uses the Dockerfile.final build pattern I’ve written about here: http://tureus.github.io/devops/2015/08/27/container-release-workflow.html . The gist: you build the application in a heavy build env (all rustc/cargo, deps, etc). Then you copy that built artifact in to a stripped down release image. It uses a docker.sock forwarding technique which is kinda neat.

The small size of the image is due to the busybox release image. The docker registry has the latest busybox image weighing in at 651KB. Busybox is uses uclibc which is not compatible with standard cargo-built binaries. You can make cargo compatible by using a musl-powered rustc but that’s a custom build. Fortunately Andrew Dunham has published some work on github: https://github.com/andrew-d/docker-rust-musl . This provides a suitable rust build environment targeting musl! He even published it to the docker registry so you can skip straight to a working image: docker pull andrewd/rust-musl.

I swapped out my debian:jessie build environment with andrewd’s rust-musl and I was off to the races.

My build environment Dockerfile:

FROM andrewd/rust-musl

RUN curl -sSL https://get.docker.io/builds/Linux/x86_64/docker-1.2.0 -o /tmp/docker && \
    echo "540459bc5d9f1cac17fe8654891814314db15e77 /tmp/docker" | sha1sum -c - && \
    mv /tmp/docker /usr/local/bin/docker && \
    chmod +x /usr/local/bin/docker

ADD . /graphite-rust
WORKDIR /graphite-rust
RUN cargo build --release --target x86_64-unknown-linux-musl

WORKDIR /graphite-rust/target/x86_64-unknown-linux-musl/release

ADD docker/Dockerfile.final /graphite-rust/target/x86_64-unknown-linux-musl/release/Dockerfile

CMD docker build -t xrlx/graphite .
  1. Install docker binary for talking to forwarded socket
  2. Copy my project’s code in to the build env
  3. Cargo fetches all the deps, does release build
  4. Change working directory to the cargo release folder
  5. Run the Dockerfile.final build to generate the release xrlx/graphite image

The Dockerfile.final:

FROM busybox

ADD carbon /usr/bin/carbon

ENV RUST_LOG debug

VOLUME /data
EXPOSE 2003
EXPOSE 2003/udp

ENTRYPOINT ["/usr/bin/carbon", "--storage-path", "/data"]

The Dockerfile.final has access to all the contents of the release folder from the build env. It copies the carbon binary in to the $PATH on the release image and sets the executable as the default entrypoint.

Hope this is useful to someone out there. Fun little hour of work to get things trimmed down. Not sure about the performance implications, haven’t really load tested it. But the application boots!