The layout command group creates and manages
OCI Image Layouts
on the local filesystem. An OCI Image Layout is a directory that stores
manifests, configs, and layer blobs content-addressed under blobs/sha256/,
with a top-level oci-layout marker and an index.json as the entry point.
layout operates entirely on the local filesystem — no registry or network
connection is required.
layout init → layout add layer (×N) → layout generate config → layout generate manifest
State between commands is tracked in a staging file (.regshape-stage.json)
automatically created by layout init inside the layout directory.
regshape layout init [OPTIONS]
regshape layout add layer [OPTIONS]
regshape layout annotate layer [OPTIONS]
regshape layout annotate manifest [OPTIONS]
regshape layout generate config [OPTIONS]
regshape layout generate manifest [OPTIONS]
regshape layout update config [OPTIONS]
regshape layout status [OPTIONS]
regshape layout show [OPTIONS]
regshape layout validate [OPTIONS]
add, annotate, generate, and update are subgroups of layout.
Initialise a new, empty OCI Image Layout directory.
Creates the oci-layout marker, blobs/sha256/ directory, an empty
index.json, and the staging file .regshape-stage.json.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Directory path to initialise as an OCI Image Layout |
- Call
init_layout(output)fromlibs/layout/operations.py. - On success, print the path of the initialised layout and exit 0.
- On
LayoutError(directory already initialised), print the error message and exit 1.
Initialised OCI Image Layout at /path/to/layout
{
"layout_path": "/path/to/layout"
}| Code | Meaning |
|---|---|
| 0 | Layout initialised successfully |
| 1 | Layout already exists at the path, or I/O error |
regshape layout init --path ./my-imageCommands under layout add add content to the layout's blob store and record
it in the staging file.
Add a layer to the layout and stage it for inclusion in the manifest.
The command first checks whether the input file is already a supported
compressed tar archive (gzip or zstd). If it is, the file is used as-is.
If it is not (e.g. a raw .tar or an unrecognised extension), it is
automatically compressed in memory using the algorithm specified by
--compress-format (default: gzip) before writing. The original
file is never modified.
After writing, the command appends the layer descriptor — including any
--annotation values — to the staging file. Annotations can also be added
or changed later with layout annotate layer.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Root directory of an initialised OCI Image Layout |
--file |
-f |
path | required | Path to the layer file |
--compress-format |
gzip|zstd |
gzip |
Compression algorithm to apply when the input is not already a supported compressed tar | |
--media-type |
string | inferred | Layer media type; if omitted, inferred from the (possibly compressed) content and confirmed interactively | |
--annotation |
key=value | None |
Annotation to store on the layer descriptor; may be specified multiple times |
The command inspects the file by magic bytes (preferred) then by extension:
| Input | Already supported? | Action |
|---|---|---|
.tar.gz / .tgz (magic \x1f\x8b) |
✓ | Use as-is |
.tar.zst (magic \xfd7zXZ\x00) |
✓ | Use as-is |
.tar (no compression) |
✗ | Compress with --compress-format (default gzip) |
| Any other extension | ✗ | Treat as raw content; compress with --compress-format |
When compression is applied the user is informed before the blob is written:
Input file is not compressed. Compressing with gzip...
Resolved after any compression is applied:
| Resulting format | Proposed Media Type |
|---|---|
| gzip-compressed tar | application/vnd.oci.image.layer.v1.tar+gzip |
| zstd-compressed tar | application/vnd.oci.image.layer.v1.tar+zstd |
| uncompressed tar | application/vnd.oci.image.layer.v1.tar |
| unknown | no default; user must supply --media-type |
If --media-type is not given:
Detected media type for layer.tar.gz:
application/vnd.oci.image.layer.v1.tar+gzip
Accept? [Y/n]:
If the user types n, they are prompted to enter the media type manually:
Enter media type: _
- Read the file at
--file. - Inspect the file by magic bytes, then by extension:
- If not a supported compressed tar, compress in memory using
--compress-format(defaultgzip) and inform the user.
- If not a supported compressed tar, compress in memory using
- Determine
media_typevia--media-typeflag or interactive prompt. - Parse
--annotationflags into adict[str, str]. - Call
stage_layer(layout, content, media_type, annotations). - Print the staged descriptor and exit 0.
- On error, print the message and exit 1.
Staged layer:
digest: sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
size: 1048576
type: application/vnd.oci.image.layer.v1.tar+gzip
{
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 1048576,
"media_type": "application/vnd.oci.image.layer.v1.tar+gzip",
"annotations": {}
}| Code | Meaning |
|---|---|
| 0 | Layer staged successfully |
| 1 | Layout not found / not initialised, file not found, compression error, or I/O error |
# Already compressed: use as-is, confirm media type interactively
regshape layout add layer --path ./my-image --file ./layer.tar.gz
# Raw tar: auto-compressed with gzip (default)
regshape layout add layer --path ./my-image --file ./layer.tar
# Raw tar: compress with zstd instead
regshape layout add layer \
--path ./my-image \
--file ./layer.tar \
--compress-format zstd
# Add with layer annotations
regshape layout add layer \
--path ./my-image \
--file ./layer.tar.gz \
--annotation org.opencontainers.image.created=2026-03-08 \
--annotation com.example.layer.role=base
# Explicit media type, no interactive prompt
regshape layout add layer \
--path ./my-image \
--file ./layer.tar.gz \
--media-type application/vnd.oci.image.layer.v1.tar+gzipCommands under layout annotate add or replace annotations after the initial
staging or generation step. Layer annotations update only the staging file;
manifest annotations re-generate the manifest blob (producing a new digest).
Add or replace annotations on a staged layer descriptor.
Looks up the layer by its 0-based index (as shown by layout status) and
merges the provided annotations into the descriptor in the staging file.
The blob file itself is not changed — only the descriptor metadata is updated.
These annotations are embedded in the manifest's layers array when
layout generate manifest is called.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Root directory of an initialised OCI Image Layout |
--index |
-i |
integer | required | 0-based index of the staged layer to annotate (see layout status) |
--annotation |
key=value | required (≥1) | Annotation to add; may be specified multiple times | |
--replace |
flag | False |
Replace all existing annotations on this layer instead of merging |
- Call
read_stage(layout)and bounds-check--index. - Merge (or replace if
--replace)--annotationvalues into the layer descriptor at that index. - Write the updated staging file atomically.
- Print the updated descriptor and exit 0.
- On error, print the message and exit 1.
Updated layer [0]:
digest: sha256:e3b0...
type: application/vnd.oci.image.layer.v1.tar+gzip
annotations:
org.opencontainers.image.created = 2026-03-08
{
"index": 0,
"digest": "sha256:e3b0...",
"media_type": "application/vnd.oci.image.layer.v1.tar+gzip",
"annotations": {
"org.opencontainers.image.created": "2026-03-08"
}
}| Code | Meaning |
|---|---|
| 0 | Annotations updated in staging file |
| 1 | Layout or staging file not found, index out of range, or I/O error |
# Add an annotation to layer 0
regshape layout annotate layer \
--path ./my-image \
--index 0 \
--annotation org.opencontainers.image.created=2026-03-08
# Replace all annotations on layer 1
regshape layout annotate layer \
--path ./my-image \
--index 1 \
--annotation com.example.role=patches \
--replaceAdd or replace manifest-level annotations on the registered manifest.
Reads the manifest blob from the blob store, updates its manifest.annotations
field, re-serialises, writes a new manifest blob, and updates both index.json
and the staging file. Because the manifest is content-addressed, this produces
a new digest.
Only available after layout generate manifest has been called.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Root directory of an initialised OCI Image Layout |
--annotation |
key=value | required (≥1) | Manifest-level annotation to add; may be specified multiple times | |
--replace |
flag | False |
Replace all existing manifest.annotations instead of merging |
- Call
read_stage(layout)and verifymanifestis notnull. - Record the old manifest digest.
- Call
read_blob(layout, manifest.digest)to fetch the current manifest bytes. - Parse the manifest JSON; merge or replace
manifest.annotations. - Re-serialise and call
add_blob(layout, updated_bytes)— yields a new digest. - Delete the old manifest blob from
blobs/sha256/<old-hex>to prevent orphaned files. - Replace the manifest descriptor in
index.json(preservingref_nameandmedia_type; updatingdigestandsize). - Update the staging file
manifestfield. - Print the updated manifest descriptor and exit 0.
- On error, print the message and exit 1.
Updated manifest:
digest: sha256:newdigest...
size: 789
media-type: application/vnd.oci.image.manifest.v1+json
ref-name: latest
annotations:
org.opencontainers.image.version = 1.0.0
{
"digest": "sha256:newdigest...",
"size": 789,
"media_type": "application/vnd.oci.image.manifest.v1+json",
"ref_name": "latest",
"annotations": {
"org.opencontainers.image.version": "1.0.0",
"org.opencontainers.image.ref.name": "latest"
}
}| Code | Meaning |
|---|---|
| 0 | Manifest updated; index.json reflects new digest |
| 1 | Manifest not yet generated, layout not initialised, or I/O error |
# Add version and creation-date annotations
regshape layout annotate manifest \
--path ./my-image \
--annotation org.opencontainers.image.version=1.0.0 \
--annotation org.opencontainers.image.created=2026-03-08
# Replace all manifest annotations
regshape layout annotate manifest \
--path ./my-image \
--annotation org.opencontainers.image.title="My Image" \
--replaceCommands under layout generate build OCI artifacts from staged content.
Generate an OCI Image Config JSON from the staged layers.
Reads the layers from the staging file, builds a config JSON containing the layer diff IDs, writes it to the blob store, and records the config descriptor in the staging file.
If --architecture or --os are not provided, the command proposes sensible
defaults and asks for confirmation.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Root directory of an initialised OCI Image Layout |
--architecture |
string | amd64 |
Target CPU architecture (proposed as default; confirmed interactively if not given) | |
--os |
string | linux |
Target operating system (proposed as default; confirmed interactively if not given) | |
--media-type |
string | application/vnd.oci.image.config.v1+json |
Config media type; proposed as default and confirmed interactively if not given | |
--annotation |
key=value (multiple) | None |
Labels embedded in the config JSON (config.Labels) |
If --architecture is not supplied:
Architecture [amd64]:
(User presses Enter to accept, or types an alternative.)
If --os is not supplied:
OS [linux]:
If --media-type is not supplied:
Config media type [application/vnd.oci.image.config.v1+json]:
- Call
read_stage(layout)to confirm layers are staged; raise if empty. - Resolve
architecture,os_name,media_typevia flags or interactive prompts. - Call
generate_config(layout, architecture, os_name, media_type, annotations). - Print the config descriptor and exit 0.
- On error, print the message and exit 1.
Generated config:
digest: sha256:abc123...
size: 234
type: application/vnd.oci.image.config.v1+json
{
"digest": "sha256:abc123...",
"size": 234,
"media_type": "application/vnd.oci.image.config.v1+json"
}| Code | Meaning |
|---|---|
| 0 | Config generated and staged |
| 1 | No layers staged, layout not initialised, or I/O error |
# Interactive prompts for all fields
regshape layout generate config --path ./my-image
# Provide all fields explicitly (no prompts)
regshape layout generate config \
--path ./my-image \
--architecture arm64 \
--os linux \
--media-type application/vnd.oci.image.config.v1+json \
--annotation org.opencontainers.image.version=1.0.0Generate an OCI Image Manifest from the staged config and layers, and
register it in index.json.
Reads the config descriptor and layer descriptors from the staging file,
builds a manifest JSON — including any --annotation values — writes it to
the blob store, and appends its descriptor to index.json.
If --ref-name or --annotation are not provided, the command prompts for
values (user may leave them blank to skip). To add or update manifest
annotations after generation, use layout annotate manifest.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Root directory of an initialised OCI Image Layout |
--ref-name |
string | None |
Human-readable reference name (e.g. latest, v1.2.3); prompted if not given |
|
--media-type |
string | application/vnd.oci.image.manifest.v1+json |
Manifest media type; proposed as default and confirmed interactively if not given | |
--annotation |
key=value (multiple) | None |
Annotations embedded in the manifest JSON |
If --ref-name is not supplied:
Reference name (e.g. latest) [skip]:
If --media-type is not supplied:
Manifest media type [application/vnd.oci.image.manifest.v1+json]:
If --annotation is not supplied:
Add manifest annotations? [y/N]:
If the user enters y, they are prompted for key=value pairs one at a
time; an empty line finishes input.
- Call
read_stage(layout)to confirm config and layers are staged. - Raise if config is not yet generated (instruct user to run
generate configfirst). - Resolve
ref_name,media_type,annotationsvia flags or interactive prompts. - Call
generate_manifest(layout, ref_name, media_type, annotations). - Print the registered manifest descriptor and exit 0.
- On error, print the message and exit 1.
Generated manifest:
digest: sha256:def456...
size: 742
media-type: application/vnd.oci.image.manifest.v1+json
ref-name: latest
{
"digest": "sha256:def456...",
"size": 742,
"media_type": "application/vnd.oci.image.manifest.v1+json",
"ref_name": "latest",
"annotations": {
"org.opencontainers.image.ref.name": "latest"
}
}| Code | Meaning |
|---|---|
| 0 | Manifest generated and registered in index.json |
| 1 | Config not yet generated, layout not initialised, or I/O error |
# Interactive prompts
regshape layout generate manifest --path ./my-image
# Explicit options
regshape layout generate manifest \
--path ./my-image \
--ref-name latest \
--annotation org.opencontainers.image.version=1.0.0Commands under layout update modify previously generated artifacts in the
staging file, replacing the old blob and descriptor with a freshly generated one.
Re-generate the OCI Image Config with updated parameters.
Useful when you want to change the architecture, OS, or labels after an initial
generate config call, without re-starting the pipeline. The staging file is
updated with the new config descriptor. If a manifest has already been generated,
a warning is printed advising you to re-run layout generate manifest.
Only available after layout generate config has been called.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Root directory of an initialised OCI Image Layout |
--architecture |
string | (keep existing) | New target CPU architecture; omit to keep current value | |
--os |
string | (keep existing) | New target OS; omit to keep current value | |
--annotation |
key=value | None |
Annotation to merge into config.Labels; may be specified multiple times |
|
--replace-annotations |
flag | False |
Replace all existing config.Labels with the --annotation values |
- Call
read_stage(layout)and verifyconfigis notnull. - Record the old config digest.
- Fetch the current config blob via
read_blob(layout, config.digest). - Apply only the fields that were explicitly passed (omitted flags keep their existing values from the current config JSON).
- Re-serialise and call
add_blob(layout, new_config_bytes). - Delete the old config blob from
blobs/sha256/<old-hex>to prevent orphaned files. - Update the staging file: replace
configwith the new descriptor. - If
manifestis notnullin the staging file, print a warning. - Print the updated config descriptor and exit 0.
- On error, print the message and exit 1.
Updated config:
digest: sha256:newcfg...
size: 241
type: application/vnd.oci.image.config.v1+json
Warning: a manifest has already been generated. Re-run 'layout generate manifest'
to reference the updated config.
{
"digest": "sha256:newcfg...",
"size": 241,
"media_type": "application/vnd.oci.image.config.v1+json",
"warning": "manifest already generated; re-run 'layout generate manifest'"
}| Code | Meaning |
|---|---|
| 0 | Config updated in staging file |
| 1 | Config not yet generated, layout not initialised, or I/O error |
# Change only the architecture
regshape layout update config \
--path ./my-image \
--architecture arm64
# Add labels to the config
regshape layout update config \
--path ./my-image \
--annotation org.opencontainers.image.version=1.0.0 \
--annotation org.opencontainers.image.vendor=Acme
# Replace all labels
regshape layout update config \
--path ./my-image \
--annotation org.opencontainers.image.title="My Image" \
--replace-annotationsShow the current staging state (staged layers, generated config, generated
manifest) from .regshape-stage.json.
Useful to check progress through the add layer → generate config → generate manifest
pipeline.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Root directory of an OCI Image Layout |
- Call
read_stage(layout). - Print the staging state and exit 0.
- On error, print the message and exit 1.
Staging state for /path/to/layout:
Layers (2):
[0] sha256:e3b0c44... (1048576 bytes, application/vnd.oci.image.layer.v1.tar+gzip)
[1] sha256:f5cd3d4... ( 524288 bytes, application/vnd.oci.image.layer.v1.tar+gzip)
Config:
sha256:abc123... (234 bytes, application/vnd.oci.image.config.v1+json)
Manifest:
sha256:def456... (742 bytes, application/vnd.oci.image.manifest.v1+json)
If config or manifest have not been generated yet, those fields show (not yet generated).
The raw contents of .regshape-stage.json:
{
"schema_version": 1,
"layers": [
{"digest": "sha256:e3b0c44...", "size": 1048576, "media_type": "application/vnd.oci.image.layer.v1.tar+gzip"},
{"digest": "sha256:f5cd3d4...", "size": 524288, "media_type": "application/vnd.oci.image.layer.v1.tar+gzip"}
],
"config": {"digest": "sha256:abc123...", "size": 234, "media_type": "application/vnd.oci.image.config.v1+json"},
"manifest": {"digest": "sha256:def456...", "size": 742, "media_type": "application/vnd.oci.image.manifest.v1+json"}
}| Code | Meaning |
|---|---|
| 0 | Status printed |
| 1 | Layout not found or staging file missing/malformed |
regshape layout status --path ./my-imagePrint the index.json of an OCI Image Layout.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Root directory of an OCI Image Layout |
- Call
read_index(layout)fromlibs/layout/operations.py. - Print the
ImageIndexserialised as pretty-printed JSON and exit 0. - On
LayoutError, print the error and exit 1.
Pretty-printed index.json:
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:def456...",
"size": 742,
"annotations": {
"org.opencontainers.image.ref.name": "latest"
}
}
]
}| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Layout not found or index.json malformed |
regshape layout show --path ./my-imageValidate the structural and content integrity of an OCI Image Layout.
| Option | Short | Type | Default | Description |
|---|---|---|---|---|
--path |
-p |
path | required | Root directory of an OCI Image Layout to validate |
- Call
validate_layout(layout)fromlibs/layout/operations.py. - If no error is raised, print a success message and exit 0.
- On
LayoutError, print the error and exit 1.
Success:
Layout at /path/to/layout is valid.
Failure (example):
Error: blob sha256:deadbeef... referenced by index.json does not exist
| Code | Meaning |
|---|---|
| 0 | Layout is structurally valid and all digests match |
| 1 | Structural violation or digest mismatch found |
regshape layout validate --path ./my-imageBuild an OCI Image Layout for a two-layer container image:
# 1. Initialise the layout
regshape layout init --path ./my-image
# 2. Add the base layer (raw tar — auto-compressed with gzip)
regshape layout add layer \
--path ./my-image \
--file ./rootfs.tar \
--annotation com.example.layer.role=base
# → "Input file is not compressed. Compressing with gzip..."
# → "Accept media type application/vnd.oci.image.layer.v1.tar+gzip? [Y/n]:" → Y
# 3. Add a second layer (already gzip — used as-is)
regshape layout add layer \
--path ./my-image \
--file ./patches.tar.gz
# 4. Add annotations to layer 1 after the fact
regshape layout annotate layer \
--path ./my-image \
--index 1 \
--annotation com.example.layer.role=patches
# 5. Check staging state
regshape layout status --path ./my-image
# 6. Generate the image config
regshape layout generate config \
--path ./my-image \
--architecture amd64 \
--os linux
# 7. (Optional) Modify the config to correct the architecture
regshape layout update config \
--path ./my-image \
--architecture arm64
# 8. Generate the manifest and register in index.json
regshape layout generate manifest \
--path ./my-image \
--ref-name latest \
--annotation org.opencontainers.image.version=1.0.0
# 9. (Optional) Add more manifest annotations after the fact
regshape layout annotate manifest \
--path ./my-image \
--annotation org.opencontainers.image.created=2026-03-08
# 10. Show the final index.json
regshape layout show --path ./my-image
# 11. Validate the layout
regshape layout validate --path ./my-image| Scenario | Message |
|---|---|
--path already initialised |
Error: <path> is already an OCI Image Layout |
--path not an initialised layout |
Error: <path> is not an OCI Image Layout (missing oci-layout file) |
--file not found |
Error: File not found: <path> |
Unknown extension, no --media-type given |
Error: cannot infer media type for '<ext>'; use --media-type |
| Compression error | Error: failed to compress <path>: <reason> |
| Layer index out of range | Error: layer index <N> out of range (staged layers: <count>) |
| No layers staged | Error: no layers have been staged; run 'layout add layer' first |
| Config not yet generated | Error: config not generated; run 'layout generate config' first |
| Manifest not yet generated | Error: manifest not generated; run 'layout generate manifest' first |
index.json malformed |
Error: index.json at <path> is not a valid OCI Image Index |
| Blob digest mismatch | Error: digest mismatch for <digest>: expected <expected>, got <actual> |
| Missing blob during validate | Error: blob <digest> referenced by <manifest-digest> does not exist |
- Wire
layoutas a Click command group insrc/regshape/cli/main.py(already done). - Implement the command group in
src/regshape/cli/layout.py. add,annotate,generate, andupdateare Click subgroups (@layout.group()).layeris a command underadd;layerandmanifestare commands underannotate;configandmanifestare commands undergenerate;configis a command underupdate.- Interactive prompts use
click.prompt()with a default value andshow_default=True; for optional open-ended annotation input useclick.confirm()+ a prompt loop. - Compression detection uses
pathlib.Path(file).suffixesfor extension check, plus reading the first 6 bytes for magic byte verification. - Compression in-memory:
gzip.compress(data)for gzip;zstandard.compress(data)for zstd. - All library calls are to
libs/layout/operations.py; the CLI layer handles only option parsing, compression, interactive prompts, output formatting, andsys.exit. - The
--jsonflag and telemetry flags follow the same conventions as other command groups.
layout
├── init
├── add
│ └── layer (compression detection + annotations)
├── annotate
│ ├── layer (update layer descriptor annotations in staging file)
│ └── manifest (re-generate manifest blob with updated annotations)
├── generate
│ ├── config (build config JSON from staged layers)
│ └── manifest (build manifest JSON from staged config + layers)
├── update
│ └── config (re-generate config with updated arch / os / labels)
├── status
├── show
└── validate