Implementation:MaterializeInc Materialize ResolvedImage Build Push
| Knowledge Sources | misc/python/materialize/mzbuild.py (ResolvedImage.build with push=True), misc/python/materialize/docker.py (image_registry)
|
|---|---|
| Domains | Build Systems, Container Registries, Artifact Publishing, CI/CD |
| Last Updated | 2026-02-08 |
Overview
Concrete implementation of content-addressed container registry publishing provided by ResolvedImage.build(prep, push=True), which builds and pushes Docker images to both Docker Hub and GHCR using fingerprint-based tags.
Description
This implementation documents the publishing pathway of the same ResolvedImage.build() method described in Implementation:MaterializeInc_Materialize_ResolvedImage_Build, but focuses specifically on the behavior when push=True is passed.
When push=True:
- Buildx is required -- Unlike local builds that can fall back to
docker build, pushing requiresdocker buildx. If buildx is not found, an error is raised with installation instructions.
- Dual-registry tagging -- The image is tagged for both Docker Hub and GHCR simultaneously:
docker.io/{image_registry}/{image_name}:mzbuild-{fingerprint}ghcr.io/materializeinc/{image_registry}/{image_name}:mzbuild-{fingerprint}
- Atomic build-and-push -- The
--pushflag is passed todocker buildx build, which builds the image and pushes it to all tagged registries in a single operation. This eliminates the gap between build and push that exists with separatedocker build+docker pushcommands.
- GHCR authentication -- If
GITHUB_GHCR_TOKENis set in the environment, the method logs intoghcr.iousingdocker loginwith thematerialize-botuser before executing the build. The token is passed via stdin to avoid command-line exposure.
The image_registry() function (from misc/python/materialize/docker.py) determines the base registry name, returning "ghcr.io/materializeinc/materialize" when MZ_GHCR is truthy (default in CI) or "materialize" (Docker Hub) otherwise.
Usage
Use ResolvedImage.build(prep, push=True) when:
- Running the
DependencySet.ensure()workflow -- This is the primary caller. It checks which images are not yet published and callsbuild(prep, push=dep.publish)for each one that needs building. - Populating the remote build cache -- After a CI build, push all newly-built images so other jobs and future builds can reuse them.
- Never for local development -- Use
push=Falsefor local builds;push=Trueis intended for CI environments with registry credentials.
Code Reference
Source Location
| File | misc/python/materialize/mzbuild.py
|
|---|---|
build() method
|
Lines 879-953 |
| Push-specific logic | Lines 900-906 (buildx requirement), 932-938 (dual-tag + --push), 940-951 (GHCR auth) |
image_registry()
|
misc/python/materialize/docker.py, Lines 213-218
|
Signature
class ResolvedImage:
def build(self, prep: dict[type[PreImage], Any], push: bool = False) -> None:
"""Build the image from source.
Requires that the caller has already acquired all dependencies and
prepared all `PreImage` actions via `PreImage.prepare_batch`.
"""
...
def image_registry() -> str:
"""Return the active container registry.
Returns 'ghcr.io/materializeinc/materialize' when MZ_GHCR is truthy
(default in CI), or 'materialize' (Docker Hub) otherwise.
"""
return (
"ghcr.io/materializeinc/materialize"
if ui.env_is_truthy("MZ_GHCR", "1")
else "materialize"
)
Import
from materialize.mzbuild import ResolvedImage
I/O Contract
Inputs
| Parameter | Type | Description |
|---|---|---|
prep |
dict[type[PreImage], Any] |
Pre-computed preparation data for each pre-image action type. |
push |
bool |
Must be True for publishing. Switches from --load to --push in the Docker command.
|
GITHUB_GHCR_TOKEN env var |
str (optional) |
GitHub Container Registry authentication token. Required for pushing to GHCR. |
MZ_GHCR env var |
str |
Controls which registry image_registry() returns. Defaults to "1" in CI.
|
self.spec() |
str |
The fully-qualified image name with fingerprint tag (e.g., materialize/environmentd:mzbuild-ABCDE...).
|
Outputs
| Output | Type | Description |
|---|---|---|
| Return value | None |
The method returns nothing; it operates via side effects. |
| Side effect: Docker Hub push | Remote registry | Image is pushed to docker.io/{spec}.
|
| Side effect: GHCR push | Remote registry | Image is pushed to ghcr.io/materializeinc/{spec}.
|
| Side effect: GHCR login | Docker daemon | If GITHUB_GHCR_TOKEN is set, the daemon is authenticated to ghcr.io.
|
Usage Examples
Publishing via DependencySet.ensure():
from pathlib import Path
from materialize.mzbuild import Repository
repo = Repository(root=Path("/path/to/materialize"))
deps = repo.resolve_dependencies([repo.images["environmentd"]])
# ensure() checks what is published, then builds + pushes missing images
deps.ensure()
# Internally, for each unpublished image:
# dep.build(prep, push=dep.publish)
# Where dep.publish is True for images marked "publish: true" in mzbuild.yml
Understanding the push command construction:
# When push=True, the buildx command includes:
# --push (instead of --load)
# -t docker.io/{spec}
# -t ghcr.io/materializeinc/{spec}
#
# Full command example:
# docker buildx build --progress=plain -f - \
# --build-arg=BUILD_PROFILE=OPTIMIZED \
# --build-arg=ARCH_GCC=x86_64 \
# --build-arg=ARCH_GO=amd64 \
# --build-arg=CI_SANITIZER=none \
# -t docker.io/materialize/environmentd:mzbuild-FINGERPRINT \
# -t ghcr.io/materializeinc/materialize/environmentd:mzbuild-FINGERPRINT \
# --platform=linux/amd64 \
# /path/to/image/dir \
# --push
GHCR authentication flow:
import os
# The build() method checks for the GHCR token before executing the build
token = os.getenv("GITHUB_GHCR_TOKEN")
if token:
# Authenticates via stdin to avoid exposing the token in process listings
# docker login ghcr.io -u materialize-bot --password-stdin
pass
The ensure() concurrency model:
# DependencySet.ensure() builds images in parallel with dependency awareness:
#
# 1. Check which images are already published (parallel, via ThreadPoolExecutor)
# 2. Prepare batch for all images that need building
# 3. Build each image in a ThreadPoolExecutor:
# - Wait (with timeout) for all dependency images to finish building
# - Call build(prep, push=True)
# - Retry up to 3 times on failure (for publishable images)
#
# This allows independent images to build concurrently while respecting
# the dependency ordering within the DAG.