DEV Community

David Tio
David Tio

Posted on • Originally published at blog.dtio.app

Boot in Seconds: Cloud Images + cloud-init in Podman

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:base image from Post #1
  • ~/vm directory from Post #3
  • genisoimage installed on your host (provides the mkisofs command)

📥 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
Enter fullscreen mode Exit fullscreen mode

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

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 qcow2 format — 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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 sysadmin with 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
Enter fullscreen mode Exit fullscreen mode

💿 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Once you see finished, the VM is ready. Log in with the username and password you set:

kvmpodman login: sysadmin
Password:
Enter fullscreen mode Exit fullscreen mode

Then shut down cleanly:

sysadmin@kvmpodman:~$ sudo poweroff
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Log in with sysadmin and the password you set. Then verify:

sysadmin@kvmpodman:~$ sudo su -
[sudo] password for sysadmin: 
root@kvmpodman:~#
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then power off:

root@kvmpodman:~# poweroff
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

🚀 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
Enter fullscreen mode Exit fullscreen mode
$ 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
$ 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
Enter fullscreen mode Exit fullscreen mode

📊 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 /
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Cloud-init version:

[sysadmin@localhost ~]$ cloud-init --version
/usr/bin/cloud-init 22.2.2
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Boot time:

real    0m19.998s
user    0m0.061s
sys     0m0.089s
Enter fullscreen mode Exit fullscreen mode

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 zypper instead of apt/dnf
  • Root filesystem is xfs, not ext4
  • Disk is extremely tight (97% used out of the box) — not ideal for installing additional packages
  • The wheel group works for sudo access
  • Older images may have cloud-init issues — grab the latest from the SUSE download center

Amazon Linux 2023:

  • Uses dnf like 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 the ec2-user default varies

✅ What You've Built

  • ✅ Ubuntu cloud image downloaded and ready to boot
  • cloud-init seed 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?

Top comments (0)