compose
sandwichcake compose -f cake.yaml resolves the base image and every
slice, composes them into a new image, pushes it to target.ref, and
writes cake.lock.
What happens
- Load —
cake.yamlis parsed with strict schema validation (unknown fields are errors). - Resolve — base and each slice ref are resolved: tags become
digests, indexes select the requested platform manifest,
layerssubsets are applied,verifyentries get their tars downloaded and scanned. With--lockedthis whole step is skipped —cake.lock’s pinned digests are used verbatim. - Check — each slice type runs its compose-time checks:
glibcrequirements against the resolved base libc, patch/base compatibility, platform mismatches. Findings print aswarn:/error:lines; errors abort unless the slice hasforce. - Compose — base layers + slice layers (in order) + config patch produce the final image or index, deterministically.
- Plan — every blob is classified for the target repo:
reuse(already present),mount(cross-repo mount),copy(must upload). Seeplanfor the rendering. - Confirm — if any bytes must be copied, an interactive prompt
asks before transferring. Non-interactive shells get an error
telling you to pass
--yes(this prevents CI from hanging on a prompt nobody can answer). - Push — configs, layers (reused/mounted where possible) and the manifest/index are written to the registry.
- Lock —
cake.lockis written next tocake.yamlwith every ref pinned to its resolved digest, plus the final output digest.
Output on success:
warn: cannot verify glibc>=2.17: base libc unknown
pushed registry.example.com/ai/infer:v1
digest sha256:8de0…
wrote cake.lockFlags
| Flag | Description |
|---|---|
-f, --file | cake.yaml path (default ./cake.yaml); cake.lock is read/written alongside it |
--dry-run | Resolve, check, compose and compute the plan — then stop. Prints dry-run complete. The plan itself is rendered only with -v |
--locked | Use cake.lock only; no tag resolution. Reproduces the locked manifest digest |
--verify | Download layers and scan tar headers during resolve (overrides per-slice verify) |
--compat | docker20: forbid zstd-compressed layers (Docker <20.10 can’t pull them) |
--yes | Skip the copy-confirmation prompt |
Examples
Normal compose with confirmation:
$ sandwichcake compose -f cake.yaml
460.2MiB will be copied to the target registry. Continue? [y/N] y
pushed registry.example.com/ai/infer:v1
digest sha256:8de0…
wrote cake.lockNon-interactive / CI usage:
sandwichcake compose -f cake.yaml --yesReproduce a build exactly (e.g. after git checkout of a release tag):
sandwichcake compose -f cake.yaml --locked --yesVerify layers and forbid zstd for an old Docker daemon:
sandwichcake compose -f cake.yaml --verify --compat docker20 --yesNotes
composeis idempotent by construction: identical resolved inputs produce an identical manifest digest, so re-running an unchanged cake.yaml reports the same digest and transfers nothing new (everything classifiesreuse).cake.lockis meant to be committed —compose --lockedneeds no tag resolution and yields bit-identical output.