A multi-stage build compiles wheels, CUDA extensions and datasets in a fat builder stage, then copies only the finished artefacts into a slim runtime stage. Everything else in the builder is discarded — it never reaches the registry. Compute how much that actually saves.
The shipped image is the runtime stage: its base image, its own layers, plus whatever COPY --from=builder pulled across. The builder's compilers, caches and intermediate object files are left behind.
The single-stage baseline used here is the honest comparison: you would have done everything in one image on top of the builder's base, so you pay for every builder layer plus the runtime stage's non-base layers.
multi_stage_mb = sum(runtime_layers) + copied_mb
single_stage_mb = sum(builder_layers) + sum(runtime_layers[1:])
saved_mb = single_stage_mb - multi_stage_mb
reduction_pct = 100 * saved_mb / single_stage_mb
Note runtime_layers[1:] — the single-stage build does not carry a second base image.
Implement:
def multi_stage_report(builder_layers, runtime_layers, copied_mb):
Return a dict with keys "multi_stage_mb", "single_stage_mb", "saved_mb" and "reduction_pct", in that order, each rounded to 2 decimal places.
print(multi_stage_report([900.0, 15.0, 640.0], [110.0, 4.0], 52.0))
Output:
{'multi_stage_mb': 166.0, 'single_stage_mb': 1559.0, 'saved_mb': 1393.0, 'reduction_pct': 89.35}
The runtime stage is 114 MB plus a 52 MB virtualenv copied from the builder, so 166 MB ships. The one-stage equivalent drags the 900 MB toolchain base and the 640 MB build cache along: 1559 MB.
print(multi_stage_report([900.0, 15.0, 640.0], [110.0, 4.0], 52.0))
{'multi_stage_mb': 166.0, 'single_stage_mb': 1559.0, 'saved_mb': 1393.0, 'reduction_pct': 89.35}Shipped = 110 + 4 + 52 = 166 MB. Single-stage = 900 + 15 + 640 (builder) + 4 (the runtime layer, minus its base image) = 1559 MB. Saved = 1393 MB, i.e. 1393 / 1559 = 89.35% smaller.
reduction_pct may be negative when the copied artefacts are larger than what the builder stage saved