tldr; Getting Zarf's zarf-injector binary down to < 512kb.

Rust Size Optimization

Stage 1's Existing Size + Behavior

Stage 1's Rust binary (zarf-injector) src performs a few operations. It's main goal is to extract the seed Crane Docker Registry image tarball (seed-image.tar) and our stage 2 binary (zarf-registry) into a running Docker container.

  1. It merges all of the zarf-payload-* configmaps back into a single tarball, then extracts to the /zarf-stage2 directory.
  2. It runs chmod 755 on the /zarf-stage2 directory (due to windows compatibility issues).

This binary is compiled against the aarch64-linux-musl-ar target for M1 Macs, and x86_64-unknown-linux-musl for x86_64 Linux.

musl is used instead of gnu because it's a smaller target. musl is a libc implementation that's used in Alpine Linux, and is more lightweight than gnu. It's also used in the scratch image, which is the smallest image possible.

x86_64_size=$(du --si target/x86_64-unknown-linux-musl/release/zarf-injector | cut -f1)
echo "x86_64 binary size: $x86_64_size"

aarch_arm_64_size=$(du --si target/release/zarf-injector | cut -f1)
echo "aarch64 binary size: $aarch_arm_64_size"


x86_64 binary size: 528k
aarch64 binary size: 373k

Not bad! But we can do better.

For reference: running a standard cargo build --release on M1 with no optimizations is ~ 700kb.

Let's Get Small

Currently, this Rust binary has the following settings in the Cargo.toml file:

opt-level = "z"  # Optimize for size.
lto = true
codegen-units = 1
panic = "abort"
strip = true

There is a great writeup here on optimizing Rust binary sizes.

From the preceding, we're already using strip Symbols from Binary, Optimize For Size, Enable Link Time Optimization, Reduce Parallel Code Generation Units, and Abort on Panic.

Following some of the other suggestions, we can get the binary size down to 156kb.

In this implementation, we're re-compiling libstd from nightly, instead of using the pre-compiled version. This is because the pre-compiled version isn't optimized for size, and isn't stripped.

"This is where build-std comes in. The build-std feature is able to compile libstd with your application from the source. It does this with the rust-src component that rustup conveniently provides." ~ min-sized-rust

rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort \
    --target aarch64-apple-darwin --release

aarch_arm_64_size=$(du --si target/aarch64-apple-darwin/release/zarf-injector | cut -f1)
echo "aarch64 binary size: $aarch_arm_64_size"

rustup +nightly target add x86_64-unknown-linux-musl
cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort \
    --target x86_64-unknown-linux-musl --release

x86_64_size=$(du --si target/x86_64-unknown-linux-musl/release/zarf-injector | cut -f1)
echo "x86_64 binary size: $x86_64_size"


aarch64 binary size: 156k
x86_64 binary size: 176k

This accomplishes a 66% decrease in binary size!

There are further optimizations that can be made, but for now this is as unstable as we want to get.