KBI packages Linux kernels and kernel-dependent artifacts as OCI images. It makes the kernel a first-class, independently versioned artifact instead of an implicit part of an OS image.
Bootable system = Kernel artifacts (KBI OCI image) + Root filesystem (standard OCI image)
Use KBI when the kernel lifecycle must be independent from user space, when add-on compatibility must be explicit, or when the same kernel needs to be reused across bare-metal, VM, and image-build environments.
A Kernel Bundle is an OCI image containing kernel artifacts in a canonical distribution layout:
/kbi/
vmlinuz
initrd optional
modules/<kver>/ optional
firmware/ optional
symvers optional
bpf/ optional
/kbi/* is a distribution namespace, not the runtime filesystem layout.
The Kernel Build Identity (KBI ID) is a deterministic identifier for a kernel build. It is computed from identity components and published as an OCI annotation:
kbi_id = "kbi:sha256:" + hex(sha256(
join("\n", sort([
"vmlinuz:" + hex(sha256(vmlinuz)),
"btf:" + hex(sha256(btf)), // when present
"config:" + hex(sha256(config)), // when present
]))
))
vmlinuz is always included. btf and config are included only when supplied at build time, so the same vmlinuz produces a different KBI ID depending on which identity inputs are present. Modules, initrd, firmware, and Module.symvers are bound through metadata such as kver, not through the KBI ID. This lets operational payloads change without redefining the kernel build identity.
Add-ons are separately packaged OCI artifacts that bind to a specific KBI ID:
- ModulePack: out-of-tree kernel modules
- BPF Pack: compiled eBPF objects plus
kbi-bpf.jsonmetadata
Pack builds are rejected unless they declare a target KBI ID, either by resolving a target image with --for or by passing --for-kbi-id directly.
The resolver combines one KBI image with zero or more add-on packs and rejects incompatible combinations before boot or install.
Today, the resolver enforces:
- pack
for_kbi_idmatches the KBI ID - architecture matches
- declared pack kernel version matches when present
- BPF packs require a target KBI with BTF
- BPF packs include manifest metadata
Signatures are enforced separately, by the signature policy (see Signing). Module signature verification and deep eBPF verification against BTF are planned but not implemented yet.
KBI currently enforces compatibility at these layers:
| Layer | Current enforcement |
|---|---|
| KBI image | required vmlinuz; valid artifact paths; bundled modules must match --kver and --arch and share one vermagic, recorded as the kernel's vermagic |
| ModulePack | required KBI binding; modules must match the target architecture and vermagic; with the target's Module.symvers, the load-time symbol checks below |
| BPF Pack | required KBI binding; required kbi-bpf.json; object references must exist; BTF required |
| Resolver | KBI ID, architecture, kernel version, BTF, and manifest checks |
The kernel can refuse a module at load time. kbi pack build --for runs the same checks at pack time, against the target KBI image:
| Check | Kernel counterpart | Needs |
|---|---|---|
ELF machine, class, and byte order match arch |
ELF header checks | always |
| Full vermagic string matches the kernel's | check_modinfo() |
bundled modules in the KBI image; otherwise only the kernel version is compared |
| Every undefined symbol is exported by the kernel or by another module in the pack | resolve_symbol() |
symvers |
Symbol CRCs match (__versions or __version_ext_crcs) |
check_version() |
symvers from a CONFIG_MODVERSIONS kernel |
| GPL-only symbols are used only by GPL-compatible modules | resolve_symbol() |
symvers |
Namespaced symbols are imported with MODULE_IMPORT_NS() |
verify_namespace_is_imported() |
symvers; relaxed when config sets CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS |
With --for-kbi-id, only the architecture check runs, since the target image is not available. Module signatures are not checked.
KBI records declared BPF dependencies, including kfuncs and kernel types/fields, but does not yet prove kfunc signatures, CO-RE relocations, or kernel structure compatibility. Those checks belong in a deeper verifier built on the same manifest and KBI BTF.
go install github.com/multikernel/kbi/cmd/kbi@latestOr build from source:
git clone https://github.com/multikernel/kbi.git
cd kbi
go build -o kbi ./cmd/kbi--kver and --arch are required because they drive install paths and add-on compatibility checks.
kbi build \
-k /boot/vmlinuz-$(uname -r) \
-i /boot/initrd.img-$(uname -r) \
-m /lib/modules/$(uname -r) \
-b /sys/kernel/btf/vmlinux \
-c /boot/config-$(uname -r) \
--symvers /usr/src/linux-headers-$(uname -r)/Module.symvers \
--kver $(uname -r) \
--arch amd64 \
-t registry.io/org/kernel:$(uname -r)kbi inspect registry.io/org/kernel:6.8.0KBI ID: kbi:sha256:3701209414c63c65...
Kernel: 6.8.0
Arch: amd64
Components: vmlinuz,initrd,btf,config,modules
Digest: sha256:a3fd5977466f1c01...
kbi push registry.io/org/kernel:6.8.0
kbi pull registry.io/org/kernel:6.8.0Registry authentication uses Docker credential helpers from ~/.docker/config.json.
kbi sign --key ci.key registry.io/org/kernel:6.8.0
kbi sign --key lab.key -a stage=tested registry.io/org/kernel:6.8.0
kbi verify --key ci.pub --key lab.pub --threshold 2 registry.io/org/kernel:6.8.0
# Or sign as part of pushing
kbi push --sign-key ci.key registry.io/org/kernel:6.8.0See Signing.
kbi install registry.io/org/kernel:6.8.0 --dest /The install adapter maps KBI artifacts to the target filesystem:
/boot/vmlinuz-<kver>
/boot/initrd.img-<kver>
/boot/config-<kver>
/boot/btf-<kver>
/boot/symvers-<kver>
/lib/modules/<kver>/
/lib/firmware/
kbi pack build \
--type modulepack \
--for registry.io/org/kernel:6.8.0 \
-m /path/to/modules/ \
-t registry.io/org/mydriver:1.0If the target KBI image is unavailable but the KBI ID is known:
kbi pack build \
--type modulepack \
-m /path/to/modules/ \
--for-kbi-id kbi:sha256:3701209414c63c65... \
--arch amd64 \
-t registry.io/org/mydriver:1.0kbi pack build \
--type bpfpack \
--for registry.io/org/kernel:6.8.0 \
--bpf /path/to/bpf/ \
-t registry.io/org/mybpf:1.0BPF packs must include kbi-bpf.json in the BPF directory, or pass it explicitly with --bpf-manifest.
{
"schema_version": 1,
"programs": [
{
"file": "trace.o",
"section": "fentry/do_sys_openat2",
"attach": "fentry",
"target": "do_sys_openat2"
}
],
"requires": {
"btf": true,
"kfuncs": ["bpf_task_acquire"],
"kernel_types": [
{"name": "task_struct", "fields": ["pid", "comm"]}
]
}
}KBI validates that the manifest is present, well-formed, and references object files inside the pack.
kbi pack inspect registry.io/org/mydriver:1.0Type: modulepack
For KBI ID: kbi:sha256:3701209414c63c65...
For Kernel: 6.8.0
Arch: amd64
Contents: mydriver.ko
Digest: sha256:ef45ab...
kbi resolve \
registry.io/org/kernel:6.8.0 \
registry.io/org/mydriver:1.0 \
registry.io/org/mybpf:1.0kbi resolve is a compatibility gate, not a full eBPF verifier. It consumes pack metadata and enforces binding, architecture, kernel version, and BTF presence.
Only vmlinuz is required. All other artifacts are optional.
| Artifact | Flag | Notes |
|---|---|---|
| vmlinuz | -k |
kernel binary |
| initrd | -i |
initial ramdisk |
| modules | -m |
directory may be /lib/modules/<kver> or its parent; module vermagic must match --kver |
| Module.symvers | --symvers |
exported symbols, CRCs, and namespaces; enables symbol checks for ModulePacks (Linux 5.10+ format) |
| config | -c |
kernel .config; participates in KBI ID |
| BTF | -b |
BPF Type Format data; participates in KBI ID |
| firmware | --firmware |
firmware directory |
KBI images are standard OCI images with custom media types per layer:
application/vnd.multikernel.kbi.vmlinuz.v1
application/vnd.multikernel.kbi.initrd.v1
application/vnd.multikernel.kbi.modules.v1.tar
application/vnd.multikernel.kbi.firmware.v1.tar
application/vnd.multikernel.kbi.kernelconfig.v1
application/vnd.multikernel.kbi.btf.v1
application/vnd.multikernel.kbi.symvers.v1
Pack images use pack-specific layer media types:
application/vnd.multikernel.kbi.modulepack.v1.tar
application/vnd.multikernel.kbi.bpfpack.v1.tar
KBI image annotations:
io.multikernel.kbi.id
io.multikernel.kbi.kver
io.multikernel.kbi.arch
io.multikernel.kbi.components
io.multikernel.kbi.vermagic when modules are bundled
Generic pack annotations:
io.multikernel.kbi.pack.type
io.multikernel.kbi.pack.for_kbi_id
io.multikernel.kbi.pack.for_kver
io.multikernel.kbi.pack.contents
io.multikernel.kbi.pack.requires
BPF pack annotations:
io.multikernel.kbi.pack.bpf.manifest
io.multikernel.kbi.pack.bpf.programs
io.multikernel.kbi.pack.bpf.kfuncs
io.multikernel.kbi.pack.bpf.types
KBI does not modify the OCI specification. KBI images and packs work with OCI-compliant registries.
Bare metal: install KBI artifacts into /boot, /lib/modules/<kver>, and /lib/firmware, then configure the bootloader normally.
VM or image build: bake the KBI into a disk image or boot it directly through a hypervisor with an external root filesystem.
Why not bootc? bootc installs whole OS images with the kernel embedded. KBI separates kernel and rootfs lifecycle.
Why not docker buildx? buildx builds generic container images. It does not understand kernel identity, typed kernel layers, module vermagic, BPF metadata, or KBI compatibility binding.
Why not LinuxKit? LinuxKit builds complete OS images. KBI makes the kernel a reusable artifact that can be composed with different root filesystems.
Why not tar plus a registry? A tarball loses typed layers, deterministic kernel identity, compatibility annotations, structured metadata, and resolver checks.
KBI’s premise is simple: the kernel is a governed resource, not an implicit dependency.
KBI and pack images are signed in their registry, after they are pushed. Signing is separate from building, so signing keys do not need to live on build machines, and an image can collect signatures over time, for example from CI when it is built and from a test lab when it passes. Signing never changes the image.
Signatures use cosign's tag layout: a companion image tagged sha256-<digest>.sig in the same repository, one layer per signature. Each layer is a simple-signing payload covering the image's manifest digest, with the signature in the dev.cosignproject.cosign/signature annotation. KBI also records the image's KBI ID in the payload, and verification rejects a signature whose recorded KBI ID differs from the image's.
The layout is compatible with cosign in both directions:
cosign verify --key ci.pub --insecure-ignore-tlog=true registry.io/org/kernel:6.8.0
kbi verify --key cosign.pub registry.io/org/kernel:6.8.0Keys are ECDSA P-256 in PEM form. Private keys must be unencrypted PKCS#8 or SEC 1; cosign's encrypted key files are not supported for signing, but cosign.pub works for verification. Transparency logs and keyless signing are not supported.
openssl ecparam -genkey -name prime256v1 -noout | openssl pkcs8 -topk8 -nocrypt -out ci.key
openssl ec -in ci.key -pubout -out ci.pubWhen a policy is configured, kbi pull, kbi install, kbi pack build --for, and kbi resolve refuse images that do not satisfy it. A pulled image is checked before it is cached. Without a policy, nothing is checked.
The policy is read from --policy, then $KBI_POLICY, then ~/.kbi/policy.json if it exists:
{
"version": 1,
"default": "reject",
"rules": [
{"match": "registry.io/org/kernel", "keys": ["ci.pub", "lab.pub"], "threshold": 2},
{"match": "registry.io/org/*", "keys": ["ci.pub"]}
]
}matchis a repository, or a prefix ending in*. The first matching rule applies.thresholdis how many distinct keys fromkeysmust have signed; the default is 1.defaultdecides images no rule matches:reject(the default) orallow.- Key paths are relative to the policy file.
Verification checks the digest of the image being used, so an image replaced in the registry, or altered in the local store, after it was signed is refused.
Planned work:
- kernel module signature verification at pack build and install time
- deeper eBPF verification against BTF
- measured boot integration
Copyright 2026 Multikernel Technologies, Inc.
Licensed under the Apache License, Version 2.0. See LICENSE for details.