Watch
1
0
Fork
You've already forked arch-uefi-cloudimg
0
Ready-to-run modern UEFI-bootable Arch Linux VM cloud image
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-06-28 20:00:29 +10:00
.forgejo/workflows Bump checkout action to v7 2026-06-28 20:00:29 +10:00
build-img-bios.sh Remove now unnecessary explicit MODULES entry 2025-11-08 16:26:48 +11:00
build-img.sh Remove now unnecessary explicit MODULES entry 2025-11-08 16:26:48 +11:00
LICENSE Initial commit 2022-05-08 04:55:56 +00:00
README.md Make use of import content type in PVE example, refresh links, other minor README adjustments 2026-06-14 14:45:59 +10:00

Arch UEFI Cloud Image (arch-uefi-cloudimg)

A (unofficial) minimal, cloud-init enabled Arch Linux virtual machine image build for UEFI booting on latest generation VM hardware.

The image targets "modern" specification Proxmox VE (or more generally QEMU/KVM) VM hardware. That is, QEMU VMs configured with UEFI (OVMF) boot, Q35 chipset, paravirtualised VirtIO storage and network etc.

Download

Image Download (compressed qcow2 format)
Checksum Download to verify

img_file=arch-uefi-cloudimg.qcow2

curl --remote-name "https://dl.bgh.io/${img_file}"
curl --remote-name "https://dl.bgh.io/${img_file}.sha384sum"
sha384sum --check "${img_file}.sha384sum"

Image automatically refreshing daily by the above Actions workflow.

Warning

It mostly goes without saying, but a quick disclaimer: files are provided as-is for non-mission-critical use. They should not be relied upon, and at times may be broken

Local Build

It should also be possible to generate the image locally by executing ./build-img.sh <output_path> on an Arch-based machine. For example, ./build-img.sh /tmp/arch-uefi-cloudimg.

The machine running the build must have internet connectivity and the following packages installed at a minimum - sudo, dosfstools, arch-install-scripts, qemu-img.

What's Included

In short, not much. And that's kind of the idea.

Keeping to the do-it-yourself nature and Arch spirit of simplicity, the only packages installed out of the box with arch-uefi-cloudimg are base, linux, systemd-ukify, zram-generator, openssh, and cloud-init (plus their dependencies). The main reason arch-uefi-cloudimg exists is to solve a bootstrapping problem. Its job is really to do just enough to facilitate VM boot and early initialisation, at which point a full configuration management solution would take over.

Typically, the VM provisioning process would go something like

  1. Use cloud-init to prime the absolute essentials for the environment, such as network configuration, SSH keys, and any management agents
  2. Once the VM is in a manageable state, have a configuration management tool handle subsequent setup

In this way, arch-uefi-cloudimg is intentionally a blank canvas.

The slightly opinionated part is perhaps the eschewment of "legacy" technologies and including + enabling zram-generator. On balance, this seemed reasonable with consensus on the age-old "to swap or not to swap" question appearing to converge towards zram (see Fedora change), zram being relatively trivial to disable post-provisioning, no upfront partitioning implications, and the assumption modern virtualised workloads would be flash storage backed.

Running

With the image downloaded (and verified), it's now time to spin up a VM. Command line examples provided below for Proxmox VE and local QEMU. These same principles can be adapted to various deployment tooling/APIs as appropriate.

Proxmox VE

Transfer image and cloud-config.yml file containing any custom cloud-init vendor/user data to PVE host

pve=user@pve.lan.example.com

scp arch-uefi-cloudimg.qcow2 "${pve}:/var/lib/vz/import/arch-uefi-cloudimg.qcow2"
scp cloud-config.yml "${pve}:/var/lib/vz/snippets/cloud-config.yml"

Use PVE host shell to create VM, set cloud-init values, and boot

storage=local-zfs

vmid=987
name=example
ip=10.0.0.123/24
gw=10.0.0.1
user=configmgmt
cat > /tmp/sshkeys << 'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIKk+Dj3QV8CYQp/JIL9JQJEfMLOFW7TpxVJEIq0BrUR configmgmt@lan.example.com
sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIBEtFzO7x6APTSMQFf8vN0/+X1YgH+5BxRr58CEz2/fAAAAAC3NzaDpleGFtcGxl
EOF

qm create "${vmid}" \
  --name "${name}" \
  --cpu host \
  --cores 2 \
  --memory 2048 \
  --ostype l26 \
  --machine q35 \
  --bios ovmf \
  --efidisk0 "${storage}:0,efitype=4m" \
  --scsihw virtio-scsi-single \
  --scsi0 "${storage}:0,import-from=/var/lib/vz/import/arch-uefi-cloudimg.qcow2,discard=on,iothread=1" \
  --bootdisk scsi0 \
  --boot c \
  --net0 virtio,bridge=vmbr0,tag=3 \
  --vga qxl \
  --agent 1 \
  --sata0 "${storage}:cloudinit" \
  --ipconfig0 "ip=${ip},gw=${gw}" \
  --ciuser "${user}" \
  --sshkeys /tmp/sshkeys \
  --cicustom "vendor=local:snippets/cloud-config.yml" \
  --tpmstate0 "${storage}:0,version=v2.0"
  # --nameserver and --searchdomain automatically inherit host settings if not specified

qm resize "${vmid}" scsi0 20G
qm start "${vmid}"
Local QEMU

Generate cloud-init NoCloud ISO

touch meta-data

cat > user-data << 'EOF'
#cloud-config
hostname: example
users:
  - name: configmgmt
    passwd: $6$cGjycsOkR1KQFQXW$MyZrZZD8o39wILwMcw8GZOGXt0nII9jHJ4eUcDrCra3gX5zAFYS7j5FoUQ4OT1b4cQlvC06y17daz8C4MWWgh1 # example
    lock_passwd: false
    sudo: ALL=(ALL) NOPASSWD:ALL
EOF

<<comment
# Default QEMU user networking (SLIRP) guest settings, IP=10.0.2.15/24, GW=10.0.2.2 (host), DNS=10.0.2.3
# Alternatively, if setting statically through cloud-init, perhaps using a bridged tap interface
cat > network-config << EOF
version: 2
ethernets:
  enp0s2:
    addresses:
      - 10.0.0.123/24
    gateway4: 10.0.0.1
    nameservers:
      addresses:
        - 10.0.0.2
      search:
        - lan.example.com
EOF
comment

cp cloud-config.yml vendor-data

xorriso -as genisoimage -output cloud-init.iso -volid CIDATA -joliet -rock meta-data user-data vendor-data #network-config

Run VM

qemu-img resize arch-uefi-cloudimg.qcow2 20G
cp /usr/share/edk2/x64/OVMF_VARS.4m.fd .

qemu-system-x86_64 \
  -enable-kvm \
  -cpu host \
  -smp cores=2 \
  -m 2G \
  -machine q35 \
  -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \
  -drive if=pflash,format=raw,file=OVMF_VARS.4m.fd \
  -object iothread,id=iothread0 \
  -device virtio-scsi-pci,iothread=iothread0 \
  -device scsi-hd,drive=scsi0 \
  -drive file=arch-uefi-cloudimg.qcow2,if=none,id=scsi0,discard=unmap \
  -nic user,model=virtio-net-pci \
  -cdrom cloud-init.iso

In either case

  • cloud-config.yml may include something like

    #cloud-config
    locale: en_US.UTF-8
    timezone: US/Eastern
    packages: qemu-guest-agent
    runcmd:
      - pacman-key --init
      - systemctl enable --now qemu-guest-agent
    

Arch-boxes

The official source of cloud-ready Arch VM images. This was the first thing I turned up in the search for a ready to run Arch cloud image.

Testing, I discovered the images were only BIOS compatible and wouldn't boot on my chosen VM hardware.

Finding an issue about adding hybrid BIOS+UEFI support, I set out to see if this was something I could tackle. Eventually coming to the realisation that, given my requirements, the added complexity to maintain legacy BIOS support and fiddling with hybrid GRUB configuration (when simpler UEFI-only boot loader alternatives like systemd-boot exist) etc wasn't something I wanted to pursue.

Archinstall

Best known as the optional archinstall installer included on Arch media. It supports guided installation as well as automation through configuration files and a Python library.

If it hadn't of been for Archinstall I mightn't have got my original start with Arch. While very useful in guided install scenarios, experimenting I increasingly felt it was a bit too "heavy", and the various options not really necessary for building a basic cloud image. I did also encounter an inconsistency here or there with configuration files not behaving as expected.

mkosi

A utility under the systemd project banner used to create custom operating system images.

I stumbled upon this very late just prior to wrapping up work on arch-uefi-cloudimg. The tool appears to provide a high degree of flexibility if the goal is to build images for several OS flavours. Another plus it being built with an eye towards systemd-nspawn containers and secure boot. It also appears to share a similar "legacy-free" philosophy to arch-uefi-cloudimg.

While yet to properly explore mkosi, I'd like to think arch-uefi-cloudimg still has its place in striking a balance between aligning to the official ArchWiki Installation guide and doing what's necessary to automate modern cloud-ready Arch Linux VM image creation.

BIOS

The unfortunate reality is many cloud/VPS providers still only support legacy BIOS boot.

For these situations, the rather paradoxically named arch-uefi-cloudimg-bios build is available. This variant includes a minimal set of changes to partitioning and uses GRUB as the boot loader.

img_file=arch-uefi-cloudimg-bios.qcow2

curl --remote-name "https://dl.bgh.io/${img_file}"
curl --remote-name "https://dl.bgh.io/${img_file}.sha384sum"
sha384sum --check "${img_file}.sha384sum"