Skip to content

Examples

End-to-end workflows showing how UMF builds compose. Each component (kernel, rootfs, bootloader) is itself an OCI artifact produced by a UMF build, so the same DSL and the same registry → cache → source build resolution chain apply at every level.

The workflows below follow that progression: build the components, then assemble them into final artifacts for each target.


Component artifacts

Building a base kernel

A kernel is a UMF build whose payload is the compiled kernel + modules. Downstream builds consume it via KERNEL <name>:<release>; the resolver pulls it from the registry, falling back to local cache, then to upstream source.

# kernel-6.12.21.umf
FROM scratch
FIRMWARE uefi
BOOTLOADER none
ROOTFS alpine:3.21
KERNEL linux:6.12.21
INITRD none

LABEL org.imagilux.umf.type=kernel
LABEL org.imagilux.umf.kernel.version=6.12.21
LABEL org.imagilux.umf.kernel.config=default

Build and publish:

umf build -t registry.example.com/kernels/linux:6.12.21 .
umf push registry.example.com/kernels/linux:6.12.21

Downstream builds that reference KERNEL linux:6.12.21 now resolve to this artifact instead of triggering an upstream source build.

Building a curated rootfs

The same pattern produces a reusable rootfs — typically an org's hardened or pre-provisioned baseline. Layer your customisation on top of a vanilla distribution rootfs, then publish under your org's namespace.

# myorg-base-1.0.umf
FROM scratch
ROOTFS debian:bookworm

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      ca-certificates curl jq systemd-resolved && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

ADD ./certs/myorg-ca.crt /usr/local/share/ca-certificates/myorg-ca.crt
RUN update-ca-certificates

LABEL org.imagilux.umf.type=rootfs
LABEL org.imagilux.umf.rootfs.org=myorg
LABEL org.imagilux.umf.rootfs.version=1.0

Publish under your org's rootfs namespace:

umf build -t registry.example.com/rootfs/myorg-base:1.0 .
umf push registry.example.com/rootfs/myorg-base:1.0

Downstream builds reference it as a normal ROOTFS:

ROOTFS myorg-base:1.0

Building a bootloader

Bootloaders follow the same shape: a UMF build whose output is the bootloader binary + assets, published under your registry. Downstream builds reference it via BOOTLOADER <name>. Use this when you need a signed or pinned bootloader (Secure Boot, fleet-locked GRUB config) instead of the upstream-tracking default.

# myorg-grub-2.12.umf
FROM scratch
ROOTFS alpine:3.21
BOOTLOADER grub

ADD ./grub.cfg /etc/default/grub
RUN grub-mkconfig -o /boot/grub/grub.cfg

LABEL org.imagilux.umf.type=bootloader
LABEL org.imagilux.umf.bootloader.flavor=grub
LABEL org.imagilux.umf.bootloader.version=2.12

Composing artifacts

Full VM image from custom components

Once your kernel and rootfs are published, a downstream VM build composes them by reference. The KERNEL and ROOTFS resolvers hit the registry — no source rebuild, no upstream pull.

FROM scratch
FIRMWARE uefi
BOOTLOADER systemd-boot
ROOTFS myorg-base:1.0
KERNEL linux:6.12.21
INITRD auto

LABEL org.imagilux.umf.author="<author>"
LABEL org.imagilux.umf.name="webserver"

RUN apt-get update && apt-get install -y nginx
ADD nginx.conf /etc/nginx/nginx.conf
EXPOSE 80/tcp
EXPOSE 443/tcp
ENABLE nginx.service

HOSTNAME webserver
TIMEZONE Europe/Paris

This is the payoff of OCI-native distribution: the heavy artifacts (kernel, rootfs) are built once per release and pulled by every downstream build.

Distro-base VM (simplified)

When you don't need a custom boot chain, FROM <distro>:<release> collapses the boot chain directives into a vanilla distro image. The boot chain is inherited from the base; you only add user-space layers.

FROM ubuntu:24.04

RUN apt-get update && apt-get install -y nginx
ADD nginx.conf /etc/nginx/nginx.conf
EXPOSE 80/tcp
ENABLE nginx.service

Target variations

The same component artifacts compose into different targets by varying the boot chain and ENTRYPOINT.

Unikernel payload

ENTRYPOINT binary runs the executable directly as PID 1 — no init system, no userland. Pair with ROOTFS none and INITRD none for the minimal unikernel shape.

FROM scratch
FIRMWARE uefi
BOOTLOADER none
KERNEL linux:6.12.21
INITRD none
ROOTFS none
ENTRYPOINT binary

ADD myapp /myapp

Container (degenerate case)

Drop the boot chain entirely — no firmware, no bootloader, no kernel, no initrd. The result is an ordinary OCI container image; runtime supplies PID 1 (ENTRYPOINT none).

FROM scratch
ROOTFS alpine:3.21
ENTRYPOINT none

RUN apk add --no-cache nginx
ADD nginx.conf /etc/nginx/nginx.conf
EXPOSE 80/tcp

For the full directive reference and resolution rules these workflows depend on, see the Specification.