Known limitations¶
The paths the reference implementation (umf v0.0.1) does not yet support, and the cases where the Specification describes something the implementation currently rejects. Each entry is a real, reachable error (not a hypothetical), so you can recognise it and reach for the documented workaround. The DSL is pre-1.0; see Stability & versioning.
For the failure messages and fixes, see Troubleshooting. For host setup, Prerequisites.
Where the spec advertises more than the implementation accepts¶
A few of these are gaps between the normative spec (which describes the intended DSL) and the current binary. They are called out inline below with a Spec vs. impl note. The spec is aspirational where marked; the binary is canonical for what runs today.
Build inputs¶
ADD <url> archive coverage¶
ADD <url> fetches the payload (size-capped, re-fetched every build with the layer cache keyed on the payload digest) and extracts archives sniffed as tar or tar.gz; anything unrecognised, .zip included (it sniffs as an opaque payload), is placed as a plain file rather than extracted. Payloads sniffing as xz / bzip2 / zstd / squashfs are rejected with fetched payload is a … archive, which is not extracted yet.
- Spec vs. impl. The ADD directive lists
.tar.xzand.zipamong the recognised archives; the engine extracts tar and tar.gz today. - A gzip source must wrap a tar. Because the payload is fingerprinted by magic number, a gzip stream is always treated as a gzipped tar: a lone
.gzof a single file, or a corrupt archive, fails the build with could not be extracted as a tar archive rather than landing as a plain file. Decompress it andADDthe result, or serve the content uncompressed. - Workaround. Repackage the resource as tar/tar.gz, or pre-extract it into the build context.
SBOM package scanning¶
umf sbom generate reads installed packages from the dpkg, apk, pacman, and sqlite rpm databases. The legacy Berkeley-DB rpm database (/var/lib/rpm/Packages, on RHEL/CentOS 7-8 and derivatives) is not read; on those images the generated SBOM lists no rpm packages. Use a base on the sqlite rpmdb (Fedora 33+, RHEL/derivatives 9+, openSUSE Leap 15.4+), or attach an externally-generated SBOM with umf sbom attach.
Bootable build step ordering¶
Multi-stage bootable builds are supported: the final stage's FROM decides the shape (a kernel artifact makes the build bootable), earlier stages build as ordinary container producers, and the final stage consumes them with cross-stage ADD --from=<stage>. Only the final stage may be bootable — an earlier stage whose FROM resolves to a kernel (nested-bootable) is rejected.
Two ordering notes carry over from the single-stage path. SHELL / USER / WORKDIR apply to subsequent RUN steps by wrapping each command host-side (WORKDIR → cd, SHELL → the interpreter argv, USER → runuser); a recipe that sets none of them runs byte-identically to before. Local, URL, and cross-stage ADDs are applied to the rootfs before the RUN steps rather than strictly interleaved in source order, so a RUN that precedes an ADD in the recipe still sees that ADD's files. Author recipes ADD-before-RUN (the conventional order) and this is invisible.
EXPOSE firewall enforcement¶
The spec describes EXPOSE as emitting an actual default-deny nftables ruleset, not just metadata. That enforcement applies only to init-system bootable images (ENTRYPOINT systemd / openrc), where the generated nftables service is enabled so the ruleset loads at boot. Two shapes do not get it:
- Container builds record the exposed ports as ordinary OCI image-config metadata only (
exposed_ports); no nftables ruleset is programmed, and the container runtime governs reachability. - Appliance bootable images (a binary-path
ENTRYPOINT, no init system) write/etc/nftables.confbut have no init to enable thenftablesservice, so the ruleset is present but not auto-loaded.
So treat EXPOSE's default-deny as a guarantee of init-system bootable images; for the other shapes, enforce reachability with your runtime or an explicit boot-time hook.
Rootless builds¶
A rootless (non-root) umf build runs inside a single user namespace UMF enters at startup. Four constraints apply; see Prerequisites and Troubleshooting.
- systemd-dependent cgroups.
RUNsteps are placed in a cgroup scope created by your user's systemd over its session bus (cgroup v2 delegation). A host with no systemd user session (a minimal image, some CI runners, a bare non-login shell) cannot place the step, and the build fails creating the cgroup. There is no rootless fs-cgroup fallback: youki's fs manager would write the root-ownedcgroup.subtree_control. Build as root, or enable a lingering user session (loginctl enable-linger). - Single-id mapping. The map is container root to your user only. Base-image files owned by a non-root uid, and
RUN --user <nonzero>, resolve tonobody. Faithful multi-uid ownership (idmapped lower layers or a subordinate range) is not yet implemented for the one-namespace path; build as root when it matters. - Rootless egress via userspace backends. Rootless
RUNsteps use a userspace egress backend selected with--rootless-net. The default isnative(in-process smoltcp gateway, no external binary).pastais an opt-in alternative requiring thepasstpackage.nonegives loopback only. The host veth plus NAT masquerade path is not used for rootless builds (it needs real root). Two caveats apply: (1)pastahas a weaker SSRF posture (coarse--no-map-gwguard only, no per-category deny); (2) name resolution undernativefails when the host's only nameserver is a loopback stub (for example, systemd-resolved127.0.0.53with no/run/systemd/resolve/resolv.confcarrying real upstreams), because loopback traffic is not routed through the gateway. Egress to literal IPs is unaffected. - Ubuntu unprivileged-userns restriction. On Ubuntu 24.04+,
kernel.apparmor_restrict_unprivileged_userns=1blocks namespace creation for the unconfined binary until you grant an AppArmor profile with theuserns,permission or relax the sysctl.
Disk projection (umf compile)¶
ext4 / erofs root partition at compile¶
umf compile writes a squashfs root partition only. An image whose boot manifest sets org.imagilux.umf.rootfs.fs=ext4 or =erofs is rejected: ext4 / erofs are not implemented; rebuild with the default squashfs rootfs.
- Spec vs. impl. The boot-manifest labels table lists
squashfs/erofs/ext4as therootfs.fsvalue set; onlysquashfsis implemented by the projector.
grub flavor¶
org.imagilux.umf.flavor accepts systemd-boot (classic) and uki. grub is reserved: umf compile rejects it (grub is reserved). An absent flavor defaults to systemd-boot with a warning; an unrecognised value is an error. Classic-flavor projection reads the bootloader .efi from inside the image rootfs (no host fallback), so a classic image shipping no bootloader is an error: switch to flavor=uki or install systemd-boot into the userland.
Real-kernel boot validation¶
Real-kernel boot is validated end to end under QEMU/KVM: a CI boot-smoke test builds a minimal kernel + busybox image, umf compiles it to a GPT/ESP/squashfs UKI disk, and boots it to a userspace marker on the serial console. Bare-metal-specific hardware is not separately tested in CI; the projected UEFI disk is byte-identical whether it boots under a VMM or on hardware, so the QEMU/KVM validation exercises the same boot contract.
VM runtime (umf run)¶
Cloud Hypervisor: firmware auto-discovery and DHCP-based port-forwarding¶
The --vmm=ch (Cloud Hypervisor) backend now does direct-kernel boot and host port-forwarding, with two caveats versus the default QEMU backend:
- Firmware is mandatory for disk boot, with no auto-discovery. Cloud Hypervisor cannot boot a raw disk without a payload, so
umf run --vmm=ch --diskrequires--firmware PATH(it does not auto-discover OVMF the way QEMU does). Direct-kernel boot needs no firmware. - Port-forwarding is host-side and needs the guest to take a DHCP lease. Unlike QEMU's user-mode
hostfwd,-p/--port-forward --vmm=chsets up a per-VM network namespace + tap + nft DNAT, with a detacheddnsmasqleasing the guest its address (gateway via the host veth). The guest must run a DHCP client, or you run your own DHCP in that namespace;umf doctorreports whetherdnsmasqis present, andnft+ipare required.
Use --vmm=qemu (the default) for auto-discovered firmware, or a guest image that does not configure its NIC.
Cross-architecture¶
--platform=<os>/<arch> selects the architecture for component resolution (base images, kernels) and for the bootable preflight (qemu-system-<arch> detection). Cross-arch container RUN execution (via binfmt_misc + qemu-user-static, as the spec's Cross-Architecture Builds describes) is a follow-up: a --platform that differs from the host arch resolves the right images but does not yet emulate foreign-arch RUN steps. Same-arch builds are unaffected.
OCI layer encoding¶
umf build emits gzip-compressed tar layers (application/vnd.oci.image.layer.v1.tar+gzip) by default and zstd (…tar+zstd, the OCI 1.1 media type) with --compression zstd. On the read side UMF transparently applies gzip-, zstd-, and uncompressed-tar layers; the build-staging unpacker is narrower and accepts gzip or plain tar only (a zstd/xz/bzip2-compressed staging archive is rejected: staging unpacks gzip or plain tar only). gzip layers interoperate with any OCI registry and with docker / podman / skopeo; zstd layers need OCI-1.1-aware consumers (current containerd / podman / skopeo read them, older Docker daemons do not).