Principle:MaterializeInc Materialize Docker Image Building
| Knowledge Sources | Container image construction, multi-stage Docker builds, cross-compilation, build system layering |
|---|---|
| Domains | Build Systems, Containerization, Cross-Compilation, CI/CD Infrastructure |
| Last Updated | 2026-02-08 |
Overview
Multi-stage container image construction orchestrates pre-image actions (such as Cargo compilation), dependency injection via custom directives, and Docker build execution to produce a final container image from heterogeneous build inputs.
Description
Building a container image from a systems-level project like Materialize is not a simple docker build invocation. It requires a layered build pipeline that coordinates multiple tools and phases:
- Pre-image actions -- Before Docker even runs, domain-specific build steps must execute. For a Rust project, this means running
cargo buildto compile binaries that will be copied into the image. These actions are defined inmzbuild.ymlas pre-image steps.
- Dependency injection via MZFROM -- Standard Dockerfiles use
FROMto reference base images by name and tag. The mzbuild system introduces a customMZFROMdirective that references other mzbuild images by name. At build time,MZFROMdirectives are rewritten to standardFROMlines with the fully-qualified image spec (including the content-addressed fingerprint tag).
- Build argument injection -- Architecture, profile, and sanitizer settings are passed as Docker
--build-argvalues, allowing the Dockerfile to conditionally include or configure components.
- Cross-compilation support -- The build targets a specific CPU architecture (via
--platform), and architecture-specific strings (GCC triplet, Go architecture) are injected as build arguments.
- Docker execution -- The prepared Dockerfile (with MZFROM rewritten) and build arguments are passed to
docker buildx build(preferred) ordocker build(fallback), producing the final image tagged with its fingerprint-based spec.
Usage
Use multi-stage container image building when:
- Constructing deployable images from source code that requires compilation in a different environment than the runtime container.
- Images have cross-image dependencies that need to be resolved at build time (not just at run time).
- Cross-compilation is required -- Building images for a different architecture than the build host (e.g., building aarch64 images on x86_64 CI machines).
- Build reproducibility is important -- The combination of pre-image actions, dependency injection, and fingerprint tagging ensures reproducible builds.
Theoretical Basis
Layered Build Systems
The image build process follows a pipeline architecture where each stage transforms or augments the build context:
[Source Code] --> [Pre-Image Actions] --> [Dockerfile Rewriting] --> [Docker Build] --> [Tagged Image]
| | | |
| cargo build MZFROM -> FROM buildx build
| copy artifacts inject fingerprint --platform
| tags for deps --build-arg
v --load/--push
mzbuild.yml
Dockerfile
This separation of concerns allows each stage to be independently configurable and composable:
- Pre-image actions are polymorphic (cargo-build, copy) and extensible.
- Dockerfile rewriting is transparent -- the Dockerfile author uses
MZFROMwithout knowing the final tag. - Docker execution adapts to the available tooling (buildx vs. legacy build).
The MZFROM Pattern
MZFROM is a dependency injection mechanism for Docker images. In a standard Docker multi-stage build, stages reference each other by index or alias within a single Dockerfile. MZFROM extends this to cross-Dockerfile references:
# Standard Docker: references within the same Dockerfile
FROM rust:1.75 AS builder
# ...
FROM debian:bookworm
COPY --from=builder /app /app
# mzbuild: references across Dockerfiles via MZFROM
MZFROM environmentd
COPY --from=0 /usr/local/bin/environmentd /usr/local/bin/
At build time, MZFROM environmentd is rewritten to something like:
FROM materialize/environmentd:mzbuild-ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
This ensures that the exact version of the dependency (identified by its fingerprint) is used.
Build Context Preparation
Before invoking Docker, the build context is cleaned with git clean -ffdX to remove all untracked and ignored files. This ensures that:
- No stale build artifacts from a previous build contaminate the current build.
- The Docker build context contains exactly the files that Git tracks, matching the inputs used for fingerprinting.
Buildx vs. Legacy Build
The system prefers docker buildx build for several advantages:
| Feature | docker build |
docker buildx build
|
|---|---|---|
| Multi-platform | Limited | Native --platform support
|
| Push support | Separate docker push |
Integrated --push flag
|
| Multi-tag | Single -t |
Multiple -t flags (Docker Hub + GHCR)
|
| Progress output | Verbose | --progress=plain for cleaner logs
|
| Load to daemon | Automatic | Requires --load flag
|
When buildx is unavailable, the system gracefully falls back to the legacy docker build command.