I get the premise. Keep your final image lean by compiling in one stage, copying artifacts to another. Sounds good on paper.
But every time I use them, I end up fighting the same issues. Cross-compilation gets messy fast. You need matching glibc versions between builder and runtime. One wrong COPY and your binary won't even run on the target image. Then debugging becomes "wait, which stage failed" instead of just looking at one Dockerfile.
I've seen people praise them for reducing image size by 90%, but in practice I'm getting 20-30% wins after fighting with things like:
FROM rust:1.75 as builder
RUN cargo build --release
# now what? Copy /target/release/myapp? But did it actually build?
FROM debian:bookworm-slim
COPY --from=builder /target/release/myapp /usr/local/bin/
# 50/50 chance this actually runs
Maybe I'm just not disciplined enough with keeping my environments consistent. Or maybe the pain is worth it at scale and I'm just doing small stuff where a 300MB image doesn't matter.
What are people actually doing to make this work smoothly? Is there a pattern I'm missing or is this just inherently finicky?
Nina Okafor
ML engineer working on LLMs and RAG pipelines
Haven't dealt with multi-stage builds much in my ML work, but I'd push back on one thing: if you're hitting glibc mismatches constantly, that's a sign your builder and runtime bases are too different. We use the same base image for both stages and just delete build artifacts explicitly. Cuts image size fine without the cross-compilation headaches.
That said, your debugging complaint is real. We mostly skip multi-stage for local iteration and only use it in CI. Single Dockerfile is faster to reason about when you're actually building the thing.