Export Formats
kldload includes kexport — a tool that converts a
running ZFS-on-root system into portable disk images for any hypervisor
or cloud platform.
This is the image factory in action. You install once. You verify once. Then kexport produces every format you need from that one verified install. The same ZFS pool, the same datasets, the same signed modules, the same boot environments — packaged as qcow2 for KVM, raw for AWS, VHD for Azure, VMDK for VMware, OVA for VirtualBox. Five platforms from one install. The image is the deployment artifact. Feed it to Packer. Deploy it with Terraform. Stamp out copies with cloud-init. The bytes that ran in your lab are the bytes running in production.
This is what "build once, deploy anywhere" actually means. Not a marketing slogan. One install, kexport all, five files, every platform on earth.
Quick reference
# Export to a specific format
kexport qcow2 # KVM / Proxmox / OpenStack
kexport raw # dd-ready sparse image
kexport vhd # Azure / Hyper-V
kexport vmdk # VMware ESXi / vSphere
kexport ova # VMware / VirtualBox portable
# Export all formats at once
kexport all
# Custom output name
KEXPORT_NAME=myserver kexport qcow2
# → myserver.qcow2
Output lands in the current directory. Files are named
kldload-export-YYYYMMDD-HHMMSS.<ext> by default.
Formats in detail
qcow2 — QEMU Copy-On-Write
kexport qcow2
- Use with: KVM, Proxmox, OpenStack, libvirt
- Features: Compressed, sparse, supports snapshots
- Typical size: 3–6GB (from a 40GB disk)
Import into Proxmox:
# SCP to Proxmox host
scp kldload-export-*.qcow2 root@proxmox:/var/lib/vz/images/
# Import as a new disk (on Proxmox host)
qm importdisk 100 /var/lib/vz/images/kldload-export-*.qcow2 local-lvm
Import into libvirt/KVM:
cp kldload-export-*.qcow2 /var/lib/libvirt/images/my-server.qcow2
virt-install \
--name my-server \
--ram 4096 --vcpus 4 \
--disk path=/var/lib/libvirt/images/my-server.qcow2 \
--os-variant centos-stream9 \
--boot uefi \
--import --noautoconsole
raw — Sparse disk image
kexport raw
- Use with:
ddto physical disk, any hypervisor, cloud providers that accept raw - Features: Sparse (doesn’t consume full disk size on filesystem)
- Typical size: Sparse file reports full size but uses only written blocks
Write to a physical disk:
dd if=kldload-export-*.raw of=/dev/sda bs=4M status=progress conv=sparse oflag=sync
sync
Convert to any other format later with qemu-img:
qemu-img convert -f raw -O qcow2 kldload-export-*.raw output.qcow2
vhd — Virtual Hard Disk
kexport vhd
- Use with: Microsoft Azure, Hyper-V, VirtualBox
- Features: Fixed subformat (required by Azure)
- Typical size: Full disk size (fixed format, not sparse)
Upload to Azure:
# Install Azure CLI
az login
# Create a managed disk from VHD
az disk create \
--resource-group mygroup \
--name kldload-disk \
--source kldload-export-*.vhd \
--os-type Linux
# Create VM from the disk
az vm create \
--resource-group mygroup \
--name kldload-vm \
--attach-os-disk kldload-disk \
--os-type Linux \
--size Standard_B2ms
Import into Hyper-V (PowerShell):
New-VM -Name "kldload" -MemoryStartupBytes 4GB -Generation 2
Add-VMHardDiskDrive -VMName "kldload" -Path "C:\VMs\kldload-export.vhd"
vmdk — VMware Virtual Machine Disk
kexport vmdk
- Use with: VMware ESXi, vSphere, Workstation, Fusion
- Features: streamOptimized subformat (efficient for transfer)
- Typical size: 3–6GB compressed
Upload to ESXi datastore:
scp kldload-export-*.vmdk root@esxi:/vmfs/volumes/datastore1/kldload/
# On ESXi, convert streamOptimized to thick/thin:
vmkfstools -i kldload-export-*.vmdk -d thin kldload-disk.vmdk
Or import via vSphere UI: New VM → Custom → Existing disk → Upload VMDK
ova — Open Virtual Appliance
kexport ova
- Use with: VMware, VirtualBox, any OVF-compatible hypervisor
- Features: Self-contained tarball with OVF metadata + VMDK disk
- Typical size: 3–6GB
The OVA bundles: - kldload.ovf — VM configuration (CPU,
RAM, disk, OS type) - kldload-disk.vmdk — The disk
image
Import into VirtualBox:
VBoxManage import kldload-export-*.ova
VBoxManage startvm kldload --type headless
Import into VMware Workstation: File → Open → select .ova
Export all formats
kexport all
Produces all five formats sequentially. Useful for publishing releases that cover every hypervisor.
Why ZFS makes image export better than any other filesystem:
Compression means smaller images. A 40GB disk with lz4 compression holds ~60GB of actual data but the used blocks are only ~3-6GB. qemu-img convert -c compresses the output, and because ZFS already compressed the data, the image is dramatically smaller than the same system on ext4. A desktop install that would be 12GB on ext4 exports as 4GB from ZFS.
Snapshot-before-export guarantees consistency. kexport snapshots before converting. The image represents a single point in time — no half-written files, no in-progress transactions, no race conditions. On ext4 you'd need to quiesce every service, sync, and hope nothing writes during the dd. On ZFS, the snapshot is atomic. The export reads from the snapshot while the live system continues running.
The image boots with ZFS superpowers. The exported image isn't a dumb disk dump. It's a ZFS pool with datasets, boot environments, and snapshot history. When someone imports your qcow2 into Proxmox, they get a system with zpool status, kbe list, and zfs snapshot working from first boot. The image carries the infrastructure with it.
How it works under the hood
kexport uses qemu-img convert for all
format conversions:
Source: /dev/zd0 (or ZFS zvol) or block device snapshot
│
├─→ qcow2: qemu-img convert -c -f raw -O qcow2
├─→ raw: qemu-img convert -f raw -O raw (sparse via conv=sparse)
├─→ vhd: qemu-img convert -f raw -O vpc -o subformat=fixed
├─→ vmdk: qemu-img convert -f raw -O vmdk -o subformat=streamOptimized
└─→ ova: vmdk + OVF XML descriptor → tar
The OVF descriptor includes hardware hints: 4 vCPUs, 4GB RAM, 1 SCSI disk, UEFI boot. These are defaults — the importing hypervisor lets you override them.
Snapshot before export
Always snapshot before exporting to ensure a consistent image:
# Snapshot the entire root pool
ksnap /
# Then export
kexport qcow2
Automating exports
The CI pipeline for golden images. This automation script is the entire image release pipeline: build ISO → install to temp VM → export all formats → upload to cloud → destroy temp VM. Run it in CI on every merge to main. Your golden image is always current. Every cloud, every hypervisor, every bare metal target gets the same verified artifact. The temp VM exists for 5 minutes. The images exist forever. That's infrastructure as code at the image layer.
Build an ISO, install to a VM, export — all scripted:
#!/bin/bash
# build-and-export.sh — produce a qcow2 from a fresh kldload install
# 1. Build the ISO
PROFILE=server ./deploy.sh build
# 2. Create a temporary VM and install
ISO=$(ls -t live-build/output/*.iso | head -1)
virt-install \
--name export-build \
--ram 4096 --vcpus 4 \
--disk size=40,format=qcow2 \
--cdrom "$ISO" \
--os-variant centos-stream9 \
--boot uefi \
--noautoconsole --wait
# 3. Export the VM disk
cp /var/lib/libvirt/images/export-build.qcow2 ./kldload-server-$(date +%Y%m%d).qcow2
# 4. Clean up
virsh undefine export-build --nvram --remove-all-storage
Size comparison (40GB disk, desktop profile, lz4 compression)
| Format | Typical size | Notes |
|---|---|---|
| qcow2 | 3.5 GB | Compressed, sparse |
| raw | 40 GB (apparent) / 4 GB (actual) | Sparse on ZFS |
| vhd | 40 GB | Fixed format (Azure requirement) |
| vmdk | 3.2 GB | streamOptimized compression |
| ova | 3.3 GB | VMDK + OVF metadata in tar |