Kibotos

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-build generates 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:

  1. Choose the image's exact digest and write the definition file.
  2. On the build host, run kibo-build template as root to get a build result directory.
  3. Run kibo template publish with an Operator identity to upload the result to the catalog.
  4. Approve this revision and set it as the default.
  5. 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, and Env are 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-build Debian 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 example kibotos-platform-kibovm_v0.2.0_amd64.tar.zst) from the release set. See kibo platform.
  • The build terminal has no Operator, ctrl, or object storage credentials loaded. If the environment has KIBOTOS_OPERATOR_*, KIBOTOS_CTRL_*, KIBOTOS_ARTIFACT_STORE_*, or KIBOTOS_S3_* variables, kibo-build refuses 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, and kibo-build picks linux/amd64 from 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"
  }
}
EOF

The fields:

FieldDescription
schemaAlways kibotos.template-definition/v1
template_idThe Template ID, referenced with --template when creating a Workspace. Each publish of the same ID is one of its revisions
display_name, descriptionShown in kibo template list and get
platform, architectureAlways linux/amd64 and x86_64
sourceThe 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_requirementsThe guest protocol and rootfs contract versions the Template requires the Platform to support. Copy the values from the example
guest.capabilitiesThe 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_semanticsAlways vsock-http-ready/v1
required_capabilitiesCapabilities the node must have. Write {} when there are no special requirements
rootfs.size_mibThe ext4 capacity in MiB, at least 256. It must hold the unpacked image and leave room for common operations in the Workspace
process_defaultsDefaults for processes started through the Workspace process interface. See the table below

All fields of process_defaults are optional:

FieldWhen not declaredDescription
pathA generic default PATHThe 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/shAn 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
userProcesses run as rootIf 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, so path puts it first. /bin/bash exists in this image. For an image such as Alpine, change shell to match the image's actual contents.
  • path and shell are 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 /data by 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 under results/, and the stage cache is under cache/stages/. To use a different directory, write root = "/srv/build-root" in /etc/kibotos/build/config.toml, or point KIBOTOS_BUILD_CONFIG or --config FILE at 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_defaults does 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:

ParameterDescription
--registry-auth-fileAn authentication file in auth.json format
--registry-cert-dirThe directory holding a private CA (*.crt) or a client certificate and private key (*.cert, *.key)
--signature-policyThe image signature policy file. When set, every build verifies signatures again, even on a cache hit
--registry-signatures-dirThe storage configuration directory for signature attachments, used with --signature-policy
  • Unsigned images are accepted by default, but every input must pass digest verification.
  • kibo-build also ignores environment variables such as REGISTRY_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_result with the path the build step actually printed. The result directory is named by its content digest, and results/ is readable by other accounts.
  • When the publishing account and the build use the same build root, kibo finds the stage cache from the same build configuration, and --build-cache-root is 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 with kibo 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 list
  • approve lets Workspaces choose this revision. set-default makes 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 CREATING longer. By default kibo workspace create waits until it is available.
  • If the command runs, the guest has booted and is ready, and the shell and path in process_defaults have 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.
  • context and containerfile are 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-mib must match rootfs.size_mib in 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_defaults in 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-layout with --oci-archive /srv/images/python.oci.tar. Archives produced by docker save are 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.

ChangeEffect
Name, description, or process_defaultsOnly reassembly. The image is not unpacked and the ext4 is not compressed again
CapacityThe ext4 is generated again, reusing the OCI preparation
Selected OCI manifest, actual host tools, or the kibo-build executableNew cache key, so the ext4 is rebuilt. Upgrading the tools or the package does this
Any file in a Containerfile build context, after .dockerignore filteringThe 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, CACHED is an image layer hit. HIT and MISS from kibo-build are 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 e2fsck checks fail, or the integrity or sensitive-field checks of the result fail.
  • /sbin/init conflicts 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

SymptomCauseFix
registry source requires an exact @sha256: referencereference is a tagResolve the digest as in Choose the image digest and fill it in
registry source must not carry a tag alongside its digestreference has both a tag and a digestRemove the tag and write repository@sha256:digest
template definition requires a rootfs size of at least 256 MiBrootfs.size_mib is below 256Increase the capacity
Out of space while unpacking the imagerootfs.size_mib cannot hold the image contentIncrease the capacity and rebuild
The command says it cannot confirm that cleanup finishedNormally 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 recoveryRun kibo-build rootfs-recover STAGING_DIRECTORY as the message shows, then retry. Recovery does not touch other builds
build process refuses inherited configuration or credentialThe build terminal has Operator, ctrl, or object storage environment variablesUse 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/stagesPass the exact path shown again. See Publishing
set-default fails with exit code 13This revision is not approved yet, or it has been withdrawnRun kibo template approve first
kibo workspace exec reports that the program is not foundThe program is not in process_defaults.path, or shell does not exist in the imageChange the definition, then rebuild and publish. Changing only these two items does not download the image again

Next steps

On this page