Build your own Template
This page walks through building your first Template from start to finish: start from a public OCI image, write a definition file, build with kibo-build, publish, approve, and create a Workspace from the new Template.
- For the tool list and handoff when the build host and the Operator workstation are separate, see Build and publish on a build host.
- For the full rules on approving, disabling, and withdrawing, see Catalog.
Overview
A Template consists of a definition file and the rootfs it produces:
- The definition file declares the content source, rootfs capacity, guest capabilities, and defaults for managed processes. The command line does not repeat this information.
- The rootfs is a read-only ext4 image that
kibo-buildgenerates by unpacking an OCI image. - The kernel, initramfs, and guest programs belong to the Platform, not the Template. When ctrl creates a Workspace, it combines the Template with a compatible Platform.
The full flow:
- Choose the image's exact digest and write the definition file.
- On the build host, run
kibo-build templateas root to get a build result directory. - Run
kibo template publishwith an Operator identity to upload the result to the catalog. - Approve this revision and set it as the default.
- Create a Workspace from it and confirm that it starts and runs commands.
Notes:
- Importing runs no program inside the image. The image's
Cmd,Entrypoint,User,WorkingDir, andEnvare recorded only as source information. The definition file decides the process environment in the Workspace. - Importing does not guarantee that every image boots on both backends, so do not skip the verification in step 5.
- Unpacking preserves numeric owners, permissions, xattrs, file capabilities, and link semantics.
- There is no self-service build service, automatic approval, image signing, or vulnerability scanning.
Prerequisites
- A Linux amd64 build host where you can run commands as root, with the
kibo-buildDebian package installed. See Standalone build tools package. Importing from an image registry needs no Docker, Go, or source code. - A working cluster and an Operator identity. See Identity and enrollment.
- An approved default Platform for the backend your nodes use. If there is none yet, publish
kibotos-platform-<backend>_<tag>_amd64.tar.zst(for examplekibotos-platform-kibovm_v0.2.0_amd64.tar.zst) from the release set. Seekibo platform. - The build terminal has no Operator, ctrl, or object storage credentials loaded. If the environment has
KIBOTOS_OPERATOR_*,KIBOTOS_CTRL_*,KIBOTOS_ARTIFACT_STORE_*, orKIBOTOS_S3_*variables,kibo-buildrefuses to run.
Steps
Choose the image digest
The definition file accepts only an exact digest, not a tag. Resolve the tag to a digest with Skopeo:
skopeo inspect --format '{{.Digest}}' docker://docker.io/library/python:3.13-slim- The output looks like
sha256:09a1…. A multi-platform image gives the digest of the image index, andkibo-buildpickslinux/amd64from it. - A tag points to a new image when upstream updates it. A digest does not. The same digest produces the same content on every build.
Write the definition file
The following writes a definition under /srv/templates/python and fills the digest from the previous step into source.reference:
digest=$(skopeo inspect --format '{{.Digest}}' docker://docker.io/library/python:3.13-slim)
install -d /srv/templates/python
cat > /srv/templates/python/template.json <<EOF
{
"schema": "kibotos.template-definition/v1",
"template_id": "python-slim",
"display_name": "Python 3.13 slim",
"description": "Python 3.13 imported from the official slim image",
"platform": "linux/amd64",
"architecture": "x86_64",
"source": {
"kind": "registry",
"reference": "docker.io/library/python@$digest"
},
"platform_requirements": {
"guest_protocol": 1,
"rootfs_contract": 2
},
"guest": {
"capabilities": ["filesystem.v1", "process.v1", "application-config.v1"],
"ready_semantics": "vsock-http-ready/v1"
},
"required_capabilities": {},
"rootfs": {
"size_mib": 2048
},
"process_defaults": {
"path": "/usr/local/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin",
"shell": "/bin/bash"
}
}
EOFThe fields:
| Field | Description |
|---|---|
schema | Always kibotos.template-definition/v1 |
template_id | The Template ID, referenced with --template when creating a Workspace. Each publish of the same ID is one of its revisions |
display_name, description | Shown in kibo template list and get |
platform, architecture | Always linux/amd64 and x86_64 |
source | The content source. When kind is registry, reference is repository@sha256:digest and cannot also carry a tag. For containerfile, see Building from a Containerfile |
platform_requirements | The guest protocol and rootfs contract versions the Template requires the Platform to support. Copy the values from the example |
guest.capabilities | The guest capabilities the Template provides. The three above are the basic capabilities for files, processes, and startup configuration. The desktop (desktop.v1) needs desktop components installed in the rootfs, and kibo workspace desktop view also needs service.unix-stream.v1; for how to write it, see the desktop recipe in the repository |
guest.ready_semantics | Always vsock-http-ready/v1 |
required_capabilities | Capabilities the node must have. Write {} when there are no special requirements |
rootfs.size_mib | The ext4 capacity in MiB, at least 256. It must hold the unpacked image and leave room for common operations in the Workspace |
process_defaults | Defaults for processes started through the Workspace process interface. See the table below |
All fields of process_defaults are optional:
| Field | When not declared | Description |
|---|---|---|
path | A generic default PATH | The full PATH value. It is not derived from the image's ENV. If the image installs programs in nonstandard directories, list them here. An empty string declares an empty PATH. environment.PATH in a request takes precedence over it, even when empty |
shell | /bin/sh | An absolute path. This program runs when the request's command line is empty. A non-empty command line runs directly with no shell added, and the SHELL environment variable does not affect the choice |
user | Processes run as root | If set, it must be a non-root account that already exists in the image's /etc/passwd. Managed processes, file operations, and desktop sessions then run as this account by default |
Notes:
- The example image installs Python in
/usr/local/bin, sopathputs it first./bin/bashexists in this image. For an image such as Alpine, changeshellto match the image's actual contents. pathandshellare not derived from the host environment or shell initialization files either. Whether the files exist and are executable is checked when a process starts. Publishing and reading reject values that contain NUL.- The Template requires a Platform that supports
application-config.v1. A Platform without it cannot be combined with the Template. - The Workspace's persistent Volume is mounted at
/databy default. The mount path is set when creating the Workspace, not in the definition.
Build
Run one command as root to download the image, generate the ext4, and assemble the Template:
template_result=$(sudo kibo-build template /srv/templates/python/template.json)
echo "$template_result"
jq '{kind, publication, provenance}' "$template_result/result.json"- Standard output prints only the path of the result directory. Progress goes to standard error.
- The build root defaults to
/var/lib/kibotos/build: results are underresults/, and the stage cache is undercache/stages/. To use a different directory, writeroot = "/srv/build-root"in/etc/kibotos/build/config.toml, or pointKIBOTOS_BUILD_CONFIGor--config FILEat another configuration file. The file may be absent, and a subcommand's explicit path parameters take precedence over it. - The result directory holds only metadata and stage references. The rootfs itself stays in the stage cache. Both must be readable when publishing.
- Rebuilding reuses completed stages. Changing only the name, capabilities, or
process_defaultsdoes not download or unpack the image again.
When the image is in a private registry, specify the access configuration with the parameters below. kibo-build does not read the root account's default authentication file or registry configuration:
| Parameter | Description |
|---|---|
--registry-auth-file | An authentication file in auth.json format |
--registry-cert-dir | The directory holding a private CA (*.crt) or a client certificate and private key (*.cert, *.key) |
--signature-policy | The image signature policy file. When set, every build verifies signatures again, even on a cache hit |
--registry-signatures-dir | The storage configuration directory for signature attachments, used with --signature-policy |
- Unsigned images are accepted by default, but every input must pass digest verification.
kibo-buildalso ignores environment variables such asREGISTRY_AUTH_FILE. Credential paths and contents are not written to any output.- Skopeo reuses the standard metadata cache in
/var/lib/containers/cache. It holds no authentication configuration and does not replace digest and signature verification.
Publish
Run this in a terminal that has the Operator identity loaded. The following assumes the publishing account is on the same machine as the build and can read the build root:
template_result=/var/lib/kibotos/build/results/kibo-build-result-sha256-0000000000000000000000000000000000000000000000000000000000000000
kibo template publish "$template_result" --json > python-publish.json
template_id=$(jq -er '.result.template.template_id' python-publish.json)
revision=$(jq -er '.result.template.revision' python-publish.json)- Replace
template_resultwith the path the build step actually printed. The result directory is named by its content digest, andresults/is readable by other accounts. - When the publishing account and the build use the same build root,
kibofinds the stage cache from the same build configuration, and--build-cache-rootis not needed. - When the build host and the Operator workstation are separate, copy the build root as described in Handoff and publishing and pass
--build-cache-root. - Publishing only registers a candidate revision in the catalog. Until it is approved, it does not appear in the default
kibo template list. View it withkibo template list --selection any.
Approve and set as default
kibo template approve "$template_id" --revision "$revision"
kibo template set-default "$template_id" --revision "$revision"
kibo template listapprovelets Workspaces choose this revision.set-defaultmakes new Workspaces that do not name a revision use it.- These two commands change only the Template catalog and do not affect the default revision of any Platform.
Create a Workspace to verify
Create a Workspace from the new Template, run one command, and delete it after confirming:
workspace_id=$(kibo workspace create --template "$template_id" --memory 1G -q)
kibo workspace exec "$workspace_id" -- python3 --version
kibo workspace delete "$workspace_id"- The first time a Template is used, the node must first download and decompress the rootfs, so the Workspace stays in
CREATINGlonger. By defaultkibo workspace createwaits until it is available. - If the command runs, the guest has booted and is ready, and the
shellandpathinprocess_defaultshave taken effect.
Building from a Containerfile
When you need to install more software on top of the image, change source to a Containerfile. kibo-build builds an OCI image with BuildKit, then generates the rootfs by the same flow:
cat > /srv/templates/python/Containerfile <<'EOF'
FROM docker.io/library/python:3.13-slim@sha256:09a17cad02668ae61ece136b4fd36ac3849e14f670ebedb637ea3e15580525bc
RUN apt-get update \
&& apt-get install -y --no-install-recommends git \
&& rm -rf /var/lib/apt/lists/*
EOF
jq '.source = {"kind": "containerfile", "context": ".", "containerfile": "Containerfile"}' \
/srv/templates/python/template.json > /srv/templates/python/template.json.new
mv /srv/templates/python/template.json.new /srv/templates/python/template.json
sudo kibo-build template /srv/templates/python/template.json- The build host needs Docker Engine and Buildx. For the tool list, see Build tools and Debian packages.
contextandcontainerfileare paths relative to the directory of the definition file and cannot point outside it.- Write the base image as
tag@sha256:digest. Otherwise each build may get a different base image. - Regular builds reuse the BuildKit layer cache. To pull the base image again and reinstall software, add
--refresh-content. - The coding, desktop, and minimal Templates in the repository are all written this way and can serve as references.
Importing a local OCI image
The definition file cannot reference a local directory directly. When the image is in a local OCI layout directory, use two steps: generate the ext4 first, then assemble with the same definition:
sudo kibo-build rootfs --oci-layout /srv/images/python \
--size-mib 2048 --output /srv/output/python.ext4
sudo kibo-build template /srv/templates/python/template.json \
--rootfs /srv/output/python.ext4--size-mibmust matchrootfs.size_mibin the definition. Assembly checks it again.- The first step also writes
/srv/output/python.ext4.json, which records the source and verification information. The output file must not exist. To pass a JSON file with another name to the second step, use--rootfs-metadata FILE. - The second step does not need root, and any account that can read the two files can run it. It does not unpack the image again. It only verifies that the ext4 and the JSON match, then assembles. If you change only the name, capabilities, or
process_defaultsin the definition, rerun only this step. - When the directory holds several images, choose one with
--source-tag. - To import from a tar archive in OCI layout format, replace
--oci-layoutwith--oci-archive /srv/images/python.oci.tar. Archives produced bydocker saveare not in this format.
Build cache
kibo-build reuses a stage result when its inputs have not changed. A cache hit revalidates the recorded inputs and the result digests.
| Change | Effect |
|---|---|
Name, description, or process_defaults | Only reassembly. The image is not unpacked and the ext4 is not compressed again |
| Capacity | The ext4 is generated again, reusing the OCI preparation |
Selected OCI manifest, actual host tools, or the kibo-build executable | New cache key, so the ext4 is rebuilt. Upgrading the tools or the package does this |
Any file in a Containerfile build context, after .dockerignore filtering | The content stage runs again. Uncommitted changes count, because the key follows file contents |
- Compression depends only on the ext4, so two Templates from the same image compress once.
- In BuildKit's progress output,
CACHEDis an image layer hit.HITandMISSfromkibo-buildare stage results. Read them separately. - A damaged result in the current format moves to quarantine and is rebuilt. An unknown format needs an explicit clean; see Cleaning the build cache.
- A cache hit does not prove that a build is deterministic. To verify that, force a rebuild as described in Rootfs build and startup testing.
Notes
- A successful build means only that the rootfs passed build-time checks: digest, platform, layer unpacking, and ext4 checks. Verify on the chosen Platform that the guest boots and the Workspace behaves correctly.
- The build fails if the kernel module directory or firmware directory in the rootfs is not empty. The Platform provides the kernel and modules.
- The build also fails when a source or blob digest does not match, the platform or media type is unsupported, a layer does not unpack, the ext4 or
e2fsckchecks fail, or the integrity or sensitive-field checks of the result fail. /sbin/initconflicts are not detected, so a successful import does not rule them out.- Publishing after the image content or the definition changes produces a new revision. Existing Workspaces keep using the revision pinned when they were created and are not updated automatically.
- For moving the default back to an older revision, and for disabling or withdrawing a revision, see Template admission.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
registry source requires an exact @sha256: reference | reference is a tag | Resolve the digest as in Choose the image digest and fill it in |
registry source must not carry a tag alongside its digest | reference has both a tag and a digest | Remove the tag and write repository@sha256:digest |
template definition requires a rootfs size of at least 256 MiB | rootfs.size_mib is below 256 | Increase the capacity |
| Out of space while unpacking the image | rootfs.size_mib cannot hold the image content | Increase the capacity and rebuild |
| The command says it cannot confirm that cleanup finished | Normally kibo-build ends its workers and removes mounts and temporary directories after a failure, and keeps stages that succeeded for the rerun. Here it kept the state and paths needed for recovery | Run kibo-build rootfs-recover STAGING_DIRECTORY as the message shows, then retry. Recovery does not touch other builds |
build process refuses inherited configuration or credential | The build terminal has Operator, ctrl, or object storage environment variables | Use a terminal that has not loaded these variables |
| Publishing shows the stage cache path to pass | --build-cache-root was given cache instead of cache/stages | Pass the exact path shown again. See Publishing |
set-default fails with exit code 13 | This revision is not approved yet, or it has been withdrawn | Run kibo template approve first |
kibo workspace exec reports that the program is not found | The program is not in process_defaults.path, or shell does not exist in the image | Change the definition, then rebuild and publish. Changing only these two items does not download the image again |
Next steps
- Build and publish on a build host: the handoff when the build host and the Operator workstation are separate, and building Platforms.
- Creating and listing: pinning a revision with
--template-revision. For resources and Volumes, see Resources and health and Volumes, export, and restore. - Catalog: approving, disabling, withdrawing, and default revisions.
Artifact reclamation
This page shows how to reclaim artifacts in object storage that are no longer referenced.
Build and publish on a build host
This page shows how to build Templates and Platforms with kibo-build on a separate build host, then hand the output to an Operator to publish to the catalog. Both image builders and Operators take part in this flow.