Skip to content

Custom Images

Capsem images are defined by profiles. Organizations create custom images by shipping profile-owned package files, root seed files, MCP config, enforcement rules, detection rules, and plugin policy. Provider access and credentials remain runtime rule/plugin truth, not image-builder truth.

Terminal window
cargo run -p capsem-admin -- profile check config/profiles/code/profile.toml --config-root config
cargo run -p capsem-admin -- image build --profile config/profiles/code/profile.toml --config-root config --arch arm64
cargo run -p capsem-admin -- manifest generate assets --version 1.3.corp.1 --json
config/
settings/
settings.toml UI/application preferences only
schema.generated.json Settings shape for UI and validation
ui-metadata.toml UI rendering metadata
corp/
corp.toml Corp locks and reporting endpoints
enforcement.toml Corp enforcement rules
detection.yaml Corp Sigma detection rules
profiles/
corp-code/
profile.toml Profile ledger
apt-packages.txt System packages
python-requirements.txt Python packages
npm-packages.txt Node CLI packages
build.sh Profile image build hook
mcp.json Profile MCP config
enforcement.toml Enforcement rules
detection.yaml Sigma detection rules
tips.txt Login tips
root/ Guest root seed
root.manifest.json Guest root seed integrity manifest
docker/
Dockerfile.rootfs.j2
Dockerfile.kernel.j2
target/config/ Generated runtime config

Images may install guest tools, but provider access, credentials, rules, and tool configuration are not image-owned. Provider/network control is profile/corp rule truth. Credentials are captured and materialized by the credential broker plugin at runtime, and logged only as BLAKE3 references.

Each profile-owned package file defines desired packages for one manager.

config/profiles/corp-code/apt-packages.txt
coreutils
util-linux
git
curl
python3
python3-pip
python3-venv
config/profiles/corp-code/python-requirements.txt
numpy
pandas
requests
pytest
{
"servers": [
{
"id": "capsem",
"name": "Capsem",
"transport": "stdio",
"command": "/run/capsem-mcp-server",
"enabled": true
}
]
}
[profiles.rules.allow_internal_registry]
name = "allow_internal_registry"
action = "allow"
match = 'http.host.matches("(^|.*\\.)registry\\.internal\\.corp$")'
[profiles.rules.block_external_search]
name = "block_external_search"
action = "block"
match = 'http.host.matches("(^|.*\\.)(google\\.com|bing\\.com|duckduckgo\\.com)$")'

Backend build parameters are implementation inputs to the profile-derived build rail and Docker templates. Do not put rootfs compression levels, Docker platforms, kernel image paths, or defconfig paths in source profiles. The release rail owns those image mechanics; profiles own which packages, root seed files, rules, MCP declarations, and plugins are part of the image.

CommandWhat it does
capsem-admin profile checkValidate profile ledger, referenced files, rules, MCP, and root seed
capsem-admin image buildBuild profile-derived kernel/rootfs assets
capsem-admin manifest generateGenerate manifest and B3SUMS for assets
capsem-admin profile materializeGenerate runtime target/config from profile and manifest

Every build produces assets/manifest.json (format 2) — a single top-level file covering every arch. It records BLAKE3 hashes and file sizes for each asset and ties asset versions to compatible binary versions:

{
"format": 2,
"refresh_policy": "24h",
"assets": {
"current": "2026.0421.30",
"releases": {
"2026.0421.30": {
"date": "2026-04-21",
"deprecated": false,
"min_binary": "1.0.0",
"arches": {
"arm64": {
"vmlinuz": {"hash": "<64-char blake3>", "size": 7797248},
"initrd.img": {"hash": "<64-char blake3>", "size": 2314963},
"rootfs.erofs": {"hash": "<64-char blake3>", "size": 454230016}
}
}
}
}
},
"binaries": {
"current": "1.0.1776688771",
"releases": {
"1.0.1776688771": {
"date": "2026-04-21",
"deprecated": false,
"min_assets": "2026.0421.30"
}
}
}
}

The runtime boots only when the asset hashes match. min_binary/min_assets gate which binary and asset versions are compatible with each other.

Source profiles do not hand-author asset hashes. capsem-admin profile materialize combines source profile/corp/settings config with the generated asset manifest into target/config for local builds, CI, packages, and installed runtime config.

The source profile is the ledger, not a generated evidence file. Do not add asset hashes, sibling-file hashes, package hashes, or build-output hashes to checked-in profile.toml. Evidence belongs in root seed manifests, asset manifests, OBOMs, build ledgers, and generated target/config.

Corporate provisioning is profile/corp driven. Do not put signing keys, catalog channels, build knobs, or release-process metadata inside corp.toml or profile.toml; those payloads should only describe runtime behavior.

The release and runtime evidence chain is:

LayerOwns
Release artifactsSBOM and provenance attestations
Corp configCorp locks, endpoints, enforcement files, detection files, and refresh_policy
Profile configVM defaults, rule files, MCP/profile metadata, asset selection, and refresh_policy
Profile assetsKernel, initrd, and rootfs bytes verified by BLAKE3

At runtime Capsem verifies BLAKE3 hashes and refresh policy before marking a profile launchable. A missing, stale, or mismatched profile/asset contract must fail closed.

Example materialized profile payload:

id = "code"
name = "Code"
revision = "2026.06.08.7"
refresh_policy = "24h"
[assets]
format = "profile-assets.v1"
refresh_policy = "on_profile_refresh"
[assets.arch.arm64.rootfs]
name = "rootfs.erofs"
url = "https://releases.capsem.dev/assets/arm64/rootfs.erofs"
hash = "blake3:..."
size = 12345678

Example corp payload:

refresh_policy = "24h"
[corp_rule_files]
enforcement = "corp/enforcement.toml"
sigma = "corp/detection.yaml"
sigma_output_endpoint = "https://siem.example.invalid/capsem/sigma"
open_telemetry = "https://otel.example.invalid/v1/traces"
remote_enforcement = "https://security.example.invalid/capsem/enforcement"
  1. Copy config/profiles/code/ to a new profile id.
  2. Edit the new profile.toml name, description, icon, and file references.
  3. Edit profile/corp security rules to allow, ask, or block network/model/MCP boundaries.
  4. Add internal guest tools only if they must be baked into the image, using profile package files or build.sh.
  5. Keep credentials brokered at runtime; do not add them to image config.
  6. Validate with capsem-admin profile check.
  7. Build with capsem-admin image build.
  8. Generate the manifest with capsem-admin manifest generate.
  9. Materialize runtime config with capsem-admin profile materialize.
  10. Distribute the package plus selected manifest and profile assets.

Block external search and allow only internal registries:

Edit the profile or corp enforcement rule file:

[profiles.rules.allow_internal_registry]
name = "allow_internal_registry"
action = "allow"
match = 'http.host.matches("(^|.*\\.)internal\\.corp\\.com$")'
[profiles.rules.block_external_search]
name = "block_external_search"
action = "block"
match = 'http.host.matches("(^|.*\\.)(google\\.com|bing\\.com|duckduckgo\\.com)$")'

Use profile-owned package files for normal package managers:

  • apt-packages.txt for apt packages
  • python-requirements.txt for Python packages
  • npm-packages.txt for Node CLI packages
  • build.sh for build-time installers that cannot be expressed as a package list

The build ledger records these declared inputs for debugging. The CI/release asset rail publishes the CycloneDX OBOM, which records the installed base-image component names and versions after the rootfs is produced.

DiagnosticCauseFix
error[E001] missing required fieldTOML config missing a schema fieldCheck file:line in error, compare against examples above
error[E304] defconfig missingKernel config for declared arch doesn’t existAdd config/kernel/defconfig.{arch}
warn[W001] no npm registrynpm packages declared but no registry configAdd a registry entry to the profile build config
warn[W005] API key in configHardcoded key in TOMLRemove it; credentials must be brokered at runtime
Build fails: “container runtime not found”No DockerInstall Docker (brew install colima docker on macOS, sudo apt install docker.io on Linux)
Build fails: exit 137 (OOM) or exit 143 (SIGTERM mid-build)Container runtime VM out of memory — Tauri install-test cold build needs >12GBBump Colima to 16GB: colima stop && colima start --vm-type vz --vz-rosetta --memory 16 --cpu 8
Build fails: “Release file not valid yet”Container VM clock driftBuilder handles this automatically via Acquire::Check-Valid-Until=false
CLI not found at runtimeInstaller put binary in /root/ which is tmpfsCopy binary to /usr/local/bin/ in the Dockerfile template