Boot in Seconds: Cloud Images + cloud-init
Quick one-liner: Skip the installer entirely — download a pre-built cloud image, seed it with cloud-init, and boot a fully configured VM in seconds.
💡 Why This Matters
Post #3 proved persistence works, but that interactive Alpine install took time. Download ISO, boot, run installer, answer prompts, wait, configure. Repeat that for every new VM and you'll spend more time installing than actually using them.
Cloud images solve this. They're pre-built disk images with an OS already installed. Ubuntu, Fedora, Debian, Rocky — they all publish ready-to-boot images. You download one, tell cloud-init your SSH key and username, and boot. No installer, no prompts, no waiting.
This post swaps the manual install for a cloud image. You'll download an Ubuntu cloud image, create a cloud-init seed, and boot straight into a configured VM.
📋 Prerequisites
-
qemu:baseimage from Post #1 -
~/vmdirectory from Post #3 -
genisoimageinstalled on your host (provides themkisofscommand)
📥 Step 1: Download a Cloud Image
Ubuntu publishes cloud images for every release. Grab the latest LTS (24.04 Noble Numbat):
$ cd ~/vm
$ curl -O https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
$ ls -lh noble-server-cloudimg-amd64.img
-rw-rw-r-- 1 user user 650M Apr 1 12:00 noble-server-cloudimg-amd64.img
That 650 MB file is a complete Ubuntu 24.04 installation, ready to boot. No install needed.
🗺️ Where to Find Cloud Images
Most major Linux distributions publish cloud images. Here's where to get them:
Open Source Distributions
| Distribution | Cloud Image Repository |
|---|---|
| Ubuntu | https://cloud-images.ubuntu.com/ |
| Debian | https://cloud.debian.org/images/ |
| Fedora | https://download.fedoraproject.org/pub/fedora/linux/releases/ |
| CentOS Stream | https://cloud.centos.org/centos/ |
| Rocky Linux | https://download.rockylinux.org/pub/rocky/9/images/x86_64/ |
| AlmaLinux | https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/ |
| openSUSE | https://download.opensuse.org/tumbleweed/appliances/ |
| Arch Linux | https://geo.mirror.pkgbuild.com/images/ |
Enterprise Distributions
| Distribution | Cloud Image Repository | Access |
|---|---|---|
| RHEL | https://access.redhat.com/downloads/content/rhel | Subscription (free developer tier) |
| SLES | https://download.suse.com/ | Account required (free trial available) |
| Oracle Linux | https://yum.oracle.com/oracle-linux-templates.html | Free |
| Amazon Linux | https://docs.aws.amazon.com/linux/al2023/ug/outside-ec2-download.html | Free |
Format tips:
- Look for
qcow2format — it's thin-provisioned and works best with QEMU/KVM - Some sites offer raw images (
.img) — these work too but take more disk space - Avoid VMDK or VDI formats — those are for VMware and VirtualBox
Note: Alpine Linux offers cloud images but uses dynamic URLs based on provider, architecture, and firmware options. Visit https://alpinelinux.org/cloud/ to generate the correct download link.
⚙️ Step 2: Create a cloud-init Seed
cloud-init is the standard for first-boot VM configuration. It runs on the first boot, reads a small YAML file, and sets up users, SSH keys, hostnames, packages — whatever you tell it to.
🔑 Get Your SSH Public Key
Cloud images don't have passwords by default — they use SSH key authentication. You'll need a key pair to log in.
Check if you already have one:
$ ls -la ~/.ssh/id_ed25519.pub
If you see a file, skip ahead — you're set. If not, generate one. ed25519 is the modern standard: faster, smaller, and more secure than RSA:
$ ssh-keygen -t ed25519 -C "your-email@example.com"
Press Enter to accept the default location (~/.ssh/id_ed25519). Optionally set a passphrase for extra security.
Then grab your public key:
$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...your-key-here... your-email@example.com
Copy the entire line — you'll paste it into the user-data file below.
🔒 Creating a Hashed Password
Cloud images accept passwords in two formats: plain text (risky) or hashed (secure). We'll use a SHA-512 hash. Use read -s to enter your password without it appearing in shell history, then pipe it to openssl:
$ read -s -p "Password: " PW && echo && openssl passwd -6 "$PW" && unset PW
$6$randomsalt$hashedpasswordstring...
The -6 flag means SHA-512. The output starts with $6$ — that's the identifier. Copy the entire string.
📝 Build the user-data File
Create the user-data file for Ubuntu:
$ mkdir -p noble
$ cat > noble/user-data << 'EOF'
#cloud-config
preserve_hostname: false
hostname: kvmpodman
users:
- name: sysadmin
groups: sudo
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...your-key-here...
lock_passwd: false
passwd: '$6$randomsalt$your-hashed-password...'
runcmd:
- echo "cloud-init completed" > /home/sysadmin/.cloud-init-done
EOF
Replace the SSH key with your own (cat ~/.ssh/id_ed25519.pub) and the passwd hash with the one you generated above.
This seed:
- Sets the hostname to
kvmpodman - Creates a user named
sysadminwith sudo access - Enables password login (for console testing)
- Leaves a marker file when cloud-init finishes
📄 Create meta-data
Create an empty meta-data file (required, but can be empty):
$ touch noble/meta-data
💿 Step 3: Build the cloud-init ISO
cloud-init reads its config from a CD-ROM attached to the VM. Use mkisofs to create a small ISO containing your seed files. Run this on your host, from ~/vm:
$ mkisofs -J -R -input-charset utf-8 -V cidata -o cloud-init-noble.iso noble/user-data noble/meta-data
You should see:
Total translation table size: 0
Total rockridge attributes bytes: 331
Total directory bytes: 0
Path table size(bytes): 10
Max brk space used 0
182 extents written (0 MB)
The -J flag enables Joliet extensions, -R adds Rock Ridge (Unix file permissions — this fixes the warning), and -V cidata sets the volume label to cidata — which is what cloud-init scans for on boot.
That small ISO contains your entire first-boot configuration.
🚀 Step 4: Boot the Cloud Image
Attach both the cloud image disk and the cloud-init ISO. The cloud image boots as normal, cloud-init detects the CD-ROM, and applies your seed on first boot:
$ podman run --rm -it \
--device /dev/kvm \
-v ~/vm:/vm:z \
qemu:base \
qemu-system-x86_64 \
-enable-kvm -cpu host \
-nographic \
-m 1024 \
-drive file=/vm/noble-server-cloudimg-amd64.img,format=qcow2 \
-cdrom /vm/cloud-init-noble.iso \
-boot c
The VM boots. Wait about 15-30 seconds for cloud-init to run. You'll see output like:
cloud-init 25.3-0ubuntu1~24.04.1 running 'modules:config' at Sat, 12 Apr 2026 12:34:56 +0000
cloud-init 25.3-0ubuntu1~24.04.1 running 'modules:final' at Sat, 12 Apr 2026 12:34:58 +0000
cloud-init 25.3-0ubuntu1~24.04.1 finished at Sat, 12 Apr 2026 12:35:02 +0000
Once you see finished, the VM is ready. Log in with the username and password you set:
kvmpodman login: sysadmin
Password:
Then shut down cleanly:
sysadmin@kvmpodman:~$ sudo poweroff
The container will exit automatically when the VM powers off, and the disk image persists.
✅ Step 5: Verify cloud-init Ran
Boot again, this time without the cloud-init ISO (it's only needed on first boot):
$ podman run --rm -it \
--device /dev/kvm \
-v ~/vm:/vm:z \
qemu:base \
qemu-system-x86_64 \
-enable-kvm -cpu host \
-nographic \
-m 1024 \
-drive file=/vm/noble-server-cloudimg-amd64.img,format=qcow2
Log in with sysadmin and the password you set. Then verify:
sysadmin@kvmpodman:~$ sudo su -
[sudo] password for sysadmin:
root@kvmpodman:~#
Check the disk layout:
root@kvmpodman:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 3.5G 0 disk
├─sda1 8:1 0 2.5G 0 part /
├─sda14 8:14 0 4M 0 part
├─sda15 8:15 0 106M 0 part /boot/efi
└─sda16 259:0 0 913M 0 part /boot
The cloud image is thin-provisioned — the disk is 3.5 GB total, with a 2.5 GB root partition, 913 MB for /boot, and 106 MB for EFI. The 4 MB sda14 is a BIOS boot partition (GRUB uses it on non-EFI boots). No swap is configured by default.
Memory-wise, this VM was given 1 GB (-m 1024), and Ubuntu reports about 961 MB available after kernel reservations.
Confirm cloud-init ran:
root@kvmpodman:~# cloud-init status
status: done
Then power off:
root@kvmpodman:~# poweroff
The container exits automatically when the VM shuts down, and the disk image persists.
🧪 Step 6: Mini Shootout — SLES 16 and Amazon Linux 2023
Cloud images skip the installer entirely, and cloud-init automates the rest of the setup. A working VM boots in seconds:
| Method | Time to Working VM |
|---|---|
| Manual install (Post #3) | 5-10 minutes |
| Cloud image + cloud-init (IDE/SATA) | ~25s |
| Cloud image + cloud-init (VirtIO) | ~10s |
Ubuntu is the easy path. With a working VM in about 10 seconds, we have plenty of time to spin up different distributions and see how they compare. Let's try two I find interesting:
SLES 16 — enterprise Linux that rarely gets covered in tutorials. Most blog posts stop at RHEL or CentOS. SLES is what shops with a SUSE subscription actually run, and it's almost nowhere to be found in container or KVM content.
Amazon Linux 2023 — I know it exists, but I've never used it outside an EC2 instance. It's built exclusively for AWS, so booting it as a native KVM VM on my own hardware is something I've been curious about.
We'll download both, build cloud-init seeds for each, and boot with VirtIO.
📥 Download the Images
Head to the links in the table above and grab the qcow2 images for your distro:
-
SLES 16: SUSE Download Center — requires a free SUSE account. Look for
SLES-16.0-Minimal-VM.x86_64-Cloud-GM.qcow2 -
Amazon Linux 2023: AWS Documentation — free, no account needed. Look for
al2023-kvm-2023.10.20260325.0-kernel-6.1-x86_64.xfs.gpt.qcow2
Drop both files into ~/vm/.
⚙️ Build cloud-init Seeds
Create a directory for each distro:
$ mkdir -p sles16 al2023
SLES 16 — uses wheel group for sudo. SLES comments out %wheel in /etc/sudoers by default, so we need to uncomment it:
$ cat > sles16/user-data << 'EOF'
#cloud-config
preserve_hostname: false
hostname: kvmpodman
users:
- name: sysadmin
groups: wheel
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...your-key-here...
lock_passwd: false
passwd: '$6$randomsalt$your-hashed-password...'
runcmd:
- sed -i 's/^#\s*%wheel\s*ALL=(ALL)\s*ALL\s*$/%wheel ALL=(ALL) ALL/' /etc/sudoers
- echo "cloud-init completed" > /home/sysadmin/.cloud-init-done
EOF
$ touch sles16/meta-data
Amazon Linux 2023 — also uses wheel:
$ cat > al2023/user-data << 'EOF'
#cloud-config
preserve_hostname: false
hostname: kvmpodman
users:
- name: sysadmin
groups: wheel
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...your-key-here...
lock_passwd: false
passwd: '$6$randomsalt$your-hashed-password...'
runcmd:
- echo "cloud-init completed" > /home/sysadmin/.cloud-init-done
EOF
$ touch al2023/meta-data
The SLES seed uses wheel and uncommented %wheel in /etc/sudoers. Amazon Linux also uses wheel but doesn't need the sed hack. The runcmd just leaves a marker file.
Build the ISOs on your host, from ~/vm:
$ mkisofs -J -R -input-charset utf-8 -V cidata -o cloud-init-sles.iso sles16/user-data sles16/meta-data
$ mkisofs -J -R -input-charset utf-8 -V cidata -o cloud-init-al2023.iso al2023/user-data al2023/meta-data
🚀 Boot
$ podman run --rm -it \
--device /dev/kvm \
-v ~/vm:/vm:z \
qemu:base \
qemu-system-x86_64 \
-enable-kvm -cpu host \
-nographic \
-m 1024 \
-drive file=/vm/SLES-16.0-Minimal-VM.x86_64-Cloud-GM.qcow2,format=qcow2,if=virtio \
-cdrom /vm/cloud-init-sles.iso \
-boot c
$ podman run --rm -it \
--device /dev/kvm \
-v ~/vm:/vm:z \
qemu:base \
qemu-system-x86_64 \
-enable-kvm -cpu host \
-nographic \
-m 1024 \
-drive file=/vm/al2023-kvm-2023.10.20260325.0-kernel-6.1-x86_64.xfs.gpt.qcow2,format=qcow2,if=virtio \
-cdrom /vm/cloud-init-al2023.iso \
-boot c
The first boot with the cloud-init ISO configures the VM. After that, drop the -cdrom flag — it's only needed once:
$ podman run --rm -it \
--device /dev/kvm \
-v ~/vm:/vm:z \
qemu:base \
qemu-system-x86_64 \
-enable-kvm -cpu host \
-nographic \
-m 1024 \
-drive file=/vm/SLES-16.0-Minimal-VM.x86_64-Cloud-GM.qcow2,format=qcow2,if=virtio
$ podman run --rm -it \
--device /dev/kvm \
-v ~/vm:/vm:z \
qemu:base \
qemu-system-x86_64 \
-enable-kvm -cpu host \
-nographic \
-m 1024 \
-drive file=/vm/al2023-kvm-2023.10.20260325.0-kernel-6.1-x86_64.xfs.gpt.qcow2,format=qcow2,if=virtio
📊 Results
| Distribution | Disk Size | Boot Time | Root Usage | Notes |
|---|---|---|---|---|
| Ubuntu 24.04 | 3.5 GB | ~10s | 72% | Lightweight server, ext4 |
| SLES 16 | 1.3 GB | ~10s | 97% | Minimal install, xfs, very tight on disk |
| Amazon Linux 2023 | 25 GB | ~20s | 7% | Cloud-optimized, xfs, plenty of room |
🦎 SLES 16 — What's Different
SLES boots fine with its own cloud-init seed. The wheel group works for sudo. Boot time:
real 0m9.855s
user 0m0.085s
sys 0m0.055s
About 10 seconds — same ballpark as Ubuntu. QEMU defaults to the right boot device when there's only one disk, so -boot c isn't needed for subsequent boots.
sysadmin@kvmpodman:~> cloud-init --version
/usr/bin/cloud-init 25.1.3-160000.1.2
The disk layout tells a different story:
sysadmin@kvmpodman:~> lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sr0 11:0 1 364K 0 rom
vda 253:0 0 1.3G 0 disk
├─vda1 253:1 0 2M 0 part
├─vda2 253:2 0 512M 0 part /boot/efi
└─vda3 253:3 0 806M 0 part /
Three partitions instead of four — no separate /boot, just EFI and root. The 2 MB vda1 is the BIOS boot partition. The root filesystem is xfs, not ext4.
And the disk is already 97% full:
sysadmin@kvmpodman:~> df -Th
Filesystem Type Size Used Avail Use% Mounted on
/dev/vda3 xfs 742M 716M 27M 97% /
/dev/vda2 vfat 512M 3.9M 508M 1% /boot/efi
27 MB of free space on root is tight. Installing a single package with zypper will likely fill the remaining space. Ubuntu gave you 688 MB free on a 3.5 GB image. SLES is a true minimal image — but that means almost no headroom.
Memory-wise, both Ubuntu and SLES report about 960 MB from the 1 GB allocation — no surprise there.
☁️ Amazon Linux 2023 — What's Different
Amazon Linux takes a completely different approach. It ships a 25 GB image with only 7% used:
[sysadmin@localhost ~]$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sr0 11:0 1 364K 0 rom
vda 252:0 0 25G 0 disk
├─vda1 252:1 0 25G 0 part /
├─vda127 259:0 0 1M 0 part
└─vda128 259:1 0 10M 0 part /boot/efi
Three partitions — a single massive 25 GB root partition, a 1 MB BIOS boot partition, and a tiny 10 MB EFI partition. No separate /boot. The filesystem is xfs, same as SLES.
[sysadmin@localhost ~]$ df -Th
Filesystem Type Size Used Avail Use% Mounted on
/dev/vda1 xfs 25G 1.7G 24G 7% /
/dev/vda128 vfat 10M 1.3M 8.7M 13% /boot/efi
24 GB of free space out of the box. This image is built for production use, not minimal testing. You can install packages, run services, and never worry about disk space.
Memory usage is similar — 964 MB total, 317 MB used:
[sysadmin@localhost ~]$ free -m
total used free shared buff/cache available
Mem: 964 317 433 2 213 508
Swap: 0 0 0
Cloud-init version:
[sysadmin@localhost ~]$ cloud-init --version
/usr/bin/cloud-init 22.2.2
One oddity: the hostname shows as localhost instead of kvmpodman. Amazon Linux ships with cloud-init 22.2.2, which is significantly older than Ubuntu's 25.3 or SLES's 25.1. The preserve_hostname: false directive may not be honored properly in this older version, or Amazon's own hostname logic overrides it during boot. Fix it manually:
[sysadmin@localhost ~]$ sudo hostnamectl set-hostname kvmpodman
Boot time:
real 0m19.998s
user 0m0.061s
sys 0m0.089s
About 20 seconds — the slowest of the three. Ubuntu and SLES both hit ~10 seconds. Amazon's extra startup time likely comes from its larger disk image, older cloud-init version, and the init services it brings up by default. Still, 20 seconds is nothing for a full production-grade Linux install.
The real kicker: this is an image designed exclusively for AWS EC2, and it boots natively as a KVM VM inside a Podman container. No AWS APIs, no special tooling — just qcow2 and VirtIO. It works.
🔍 Distro Comparison
| Metric | Ubuntu 24.04 | SLES 16 | Amazon Linux 2023 |
|---|---|---|---|
| Disk size | 3.5 GB | 1.3 GB | 25 GB |
| Root usage | 72% | 97% | 7% |
| Filesystem | ext4 | xfs | xfs |
| Boot time | ~10s | ~10s | ~20s |
| cloud-init | 25.3 | 25.1 | 22.2 |
| Partitions | 4 | 3 | 3 |
| Swap | None | None | None |
⚠️ Distro-Specific Gotchas
SLES:
- Uses
zypperinstead ofapt/dnf - Root filesystem is xfs, not ext4
- Disk is extremely tight (97% used out of the box) — not ideal for installing additional packages
- The
wheelgroup works for sudo access - Older images may have cloud-init issues — grab the latest from the SUSE download center
Amazon Linux 2023:
- Uses
dnflike RHEL/CentOS - Comes with cloud-init pre-installed (it's designed for AWS)
- May try to reach AWS metadata services on boot — harmless but adds a few seconds
- Default shell for root is
bash, but theec2-userdefault varies
✅ What You've Built
- ✅ Ubuntu cloud image downloaded and ready to boot
- ✅
cloud-initseed with user, SSH key, and hashed password - ✅ VM boots in under 10 seconds with VirtIO, fully configured
- ✅ No manual installer, no interactive prompts
- ✅ SLES 16 and Amazon Linux 2023 running side by side
🔜 What's Next?
You can boot into the VM, but you're stuck in the console. Typing commands in the QEMU terminal is clunky — no copy/paste, no multiple windows, no real terminal features.
Post #5: We'll set up SSH networking so you can connect from your host terminal, use your favorite SSH client, and treat this VM like a real remote server.
This guide is Part 4 of the KVM Virtual Machines on Podman series.
Part 1: Build a KVM-Ready Container Image from Scratch
Part 2: KVM Acceleration in a Rootless Podman Container
Part 3: Persistent VMs in Podman: Install Alpine to a qcow2 Disk Image
Coming up in Part 5: SSH Into Your Podman VM — Container Networking for KVM
Found this helpful?
- LinkedIn: Share with your network
- Twitter: Tweet about it
- Questions? Drop a comment below or reach out on LinkedIn
Top comments (0)