Skip to content

Authoring an image

What you provide

A VM needs a kernel, an initrd, a kernel command line, and an init system. Supplying these is the author's job. contemper does not install a kernel, generate an initrd, choose kernel parameters, or set up an init system.

This is not an extra step contemper invents. Installing a kernel and an init system is what installing an operating system is; contemper changes which tool drives that step, not how much work it is. In practice it's a line in your build file, RUN apk add linux-virt openrc or your distro's equivalent: the same package from the same package manager you'd use anywhere else.

Stock container bases don't carry these components, and that's correct of them rather than a deficiency. alpine:latest has no kernel because the host's kernel is already there, and no OpenRC because the container runtime is PID 1. In a container they'd be inert weight. A VM is a different execution model, and those roles become yours to fill.

Three clarifications:

  • contemper has no opinion on which init system, or (for its own purposes) whether one exists at all. The requirement comes from reality, since a Linux VM needs one to boot, not from contemper.
  • contemper has no opinion on how a booted system gets its first-boot configuration. cloud-init is one common answer, but that's a property of your image and your provider. A target's support image may require such a mechanism, in which case it declares that explicitly.
  • Generate your initrd in generic mode, not host-only mode. dracut and mkinitcpio default to probing the hardware of the machine they run on, which during a container build is your build host rather than the VM that will boot the image. Pass --no-hostonly or the equivalent; for mkinitcpio, that means dropping its autodetect hook from HOOKS in /etc/mkinitcpio.conf, since that hook is what makes its initrd host-only in the first place. Distributions lists the setting for each family.

The fixed-path contract

Kernel, initrd and an optional command line go at fixed paths, the same for every target:

Path Meaning
/boot/contemper/vmlinuz the kernel
/boot/contemper/initrd the initrd (generic, not host-only)
/boot/contemper/cmdline optional; extra kernel command line parameters, e.g. console=
/sbin/init must exist
/etc/os-release optional; if present, embedded in the boot image

Symlinks are fine: /boot/contemper/vmlinuz → ../vmlinuz-virt is how the example does it. They are resolved inside the image's filesystem, never on the host, with a hop limit and clamping at /.

The cmdline file is optional, and so is anything in it: contemper always writes root=LABEL=contemper-root at the front of the kernel command line, since it creates that partition itself and labels it contemper-root. There's no bootloader installer to generate the rest for you, so leave the file out, or leave it blank, unless you have parameters to add beyond root= — extra options such as console=, for example:

rootfstype=ext4 rw console=ttyAMA0

Use console=ttyS0 on x86-64. Your part comes after contemper's, so a root= of your own takes precedence. Resolving LABEL= is done by the initrd; every common initrd generator (dracut, initramfs-tools, mkinitcpio, Alpine's mkinitfs) supports it. Refer to the same label in /etc/fstab:

LABEL=contemper-root / ext4 rw,relatime 0 1

With initramfs-tools, also set FSTYPE=ext4 in /etc/initramfs-tools/initramfs.conf before building the initrd: its fsck hook can't resolve LABEL= at image build time and would otherwise leave fsck.ext4 out, so the root filesystem would not be checked at boot.

Prefer to bring your own bootloader and update the kernel in place instead? That is a different mode with its own contract; see Bootloader images.

Marking the image ready

Mark an image as intended for contemper in the build file:

LABEL io.contemper.ready="true"

It asserts intent only. It does not imply the requirements above are met; those are checked later, against the merged filesystem, and an unprepared image fails there regardless.

What the label buys is where that failure happens. It lives in the image config, so it can be read and rejected before a single layer is transferred. Rejecting an image for a few kilobytes rather than after pulling gigabytes is the difference between a cheap error and an expensive one.

What contemper reads from your image

contemper never parses your build file; it reads the built image. Most build instructions leave no trace there at all: RUN, COPY, ADD, ARG and FROM produce the filesystem, which is the input. A handful of instructions record fields in the image config, and contemper reads only some of them.

Instruction contemper
LABEL read: readiness marker and contemper configuration
VOLUME read: named, sized and formatted on first boot - see Volumes
EXPOSE, HEALTHCHECK read: recorded as deployment hints; not used for the disk
ENTRYPOINT, CMD ignored
USER, WORKDIR ignored
ENV ignored
STOPSIGNAL ignored; shutdown is your init system's job
ONBUILD ignored; its triggers fire on a later build, never here

Ignored means exactly that: contemper reads the field, uses it for nothing, and does not complain. It never fails a build over a config field it doesn't consume, because most of these are inherited from your base image rather than written by you. FROM alpine sets a CMD you never typed.

Your CMD does not run. In a container the runtime executes your entrypoint as PID 1. In a VM, PID 1 is your init system. To start your workload, ship a service for it in your image (an OpenRC service, a systemd unit, whatever your init system uses), exactly as you would when installing software on any other machine.

The same applies to ENV: there is no container runtime to inject it into your process environment, so set what your service needs the way your init system expects. A target's support image may bridge some of these; that's up to the support image, not contemper.

A complete example

The repository's examples/alpine/Containerfile is a minimal image that satisfies all of the above: a kernel, a generic initrd built with mkinitfs, a command line, OpenRC with a serial login, and a boot marker printed to the console. Getting started walks through building and booting it. examples/debian/Containerfile does the same with systemd as init and initramfs-tools generating the initrd, for a merged-/usr, systemd-based starting point instead. examples/archlinux/Containerfile is the same again with mkinitcpio generating the initrd (amd64 only, since that's all the official archlinux base image is published for). For other distributions, see Distributions, which has notes per family and a Containerfile for each.