Skip to content

Instantly share code, notes, and snippets.

@ncopa
Created January 2, 2026 17:28
Show Gist options
  • Select an option

  • Save ncopa/00c6e791e3e56c6e0369e5a2aa2eb99f to your computer and use it in GitHub Desktop.

Select an option

Save ncopa/00c6e791e3e56c6e0369e5a2aa2eb99f to your computer and use it in GitHub Desktop.
Create UKI image from Alpine

Create Alpine Linux unified kernel image with netboot

This explains how to create an Alpine Linux unified kernel image (UKI) with netboot.

For this we need:

  • a kernel
  • an initramfs image
  • a modloop image (a squashfs image with the kernel modules for the kernel)
  • the ukify tool from systemd

The kernel and initramfs

Download the latest netboot tarball from https://cdn.alpinelinux.org/ For example: https://dl-cdn.alpinelinux.org/alpine/v3.23/releases/x86_64/alpine-netboot-3.23.2-x86_64.tar.gz

Extract the image: tar -zxf alpine-netboot-3.23.2-x86_64.tar.gz

The kernel, initramfs and modloop are found in the boot/ directory:

boot/
boot/System.map-6.18.1-0-virt
boot/config-6.18.1-0-virt
boot/initramfs-virt
boot/modloop-virt
boot/vmlinuz-virt
boot/dtbs-virt/
boot/System.map-6.18.1-0-lts
boot/config-6.18.1-0-lts
boot/initramfs-lts
boot/modloop-lts
boot/vmlinuz-lts
boot/dtbs-lts/

Install ukify tool

apk add ukify

Create the image

We use the -virt flavored kernel, which is optimized for virtual machines and is significantly lighter than the -lts flavored kernel.

We set alpine_repo and modloop to a http repo because we don't have the certificates for https.

At this point it is non-trivial to embed the modloop in the initramfs, so we tell alpine to download it from network via the modloop=<URL> boot option.

We also set console=ttyS0 for serial console.

ukify build \
  --linux=./boot/vmlinuz-virt \
  --initrd=./boot/initramfs-virt \
  --cmdline="alpine_repo=http://dl-cdn.alpinelinux.org/alpine/v3.23/main modloop=http://dl-cdn.alpinelinux.org/alpine/v3.23/releases/x86_64/netboot-3.23.2/modloop-virt console=ttyS0" \
  --output alpine-virt-3.23.2.efi

Boot the image with qemu

Boot the image with qemu, using a virtio network device:

qemu-system-x86_64 \
  -accel kvm \
  -cpu host \
  -nic user,model=virtio-net-pci \
  -m 1024 \
  -nographic \
  -bios /usr/share/ovmf/bios.bin 
  -kernel ./alpine-virt-3.23.2.efi

Boot with ssh keys from user-data

You can also create a tiny-cloud (a mini cloud-init implementation) and pass it to qemu via the network.

Create the meta-data and user-data files:

cat >meta-data<<EOF
hostname: alpine-vm
EOF

cat >user-data<<EOF
#cloud-config
ssh_authorized_keys:
  - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOIiHcbg/7ytfLFHUNLRgEAubFz/13SwXBOM/05GNZe4 ncopa@ncopa-desktop
EOF

Spin up a tiny http server to serve those files:

python -m http.server --directory .

From a second console, run qemu with -smbios option where we set the nocloud data source URL to qemu's default IP address. We can also port forward localhost:22000 to the VM so we can ssh in to the VM.

qemu-system-x86_64 \
  -accel kvm \
  -cpu host \
  -netdev user,id=net0,hostfwd=tcp:127.0.0.1:22000-:22 \
  -device virtio-net-pci,netdev=net0 \
  -m 1024 \
  -nographic \
  -bios /usr/share/ovmf/bios.bin \
  -smbios type=1,serial=ds='nocloud;s=http://10.0.2.2:8000/' \
  -kernel ./alpine-virt-3.23.2.efi

Once it is booted you can ssh in via port 22000

ssh -p 22000 [email protected]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment