Building Baremetal Images with Disk Image Builder¶
This guide covers building Ubuntu QCOW2 images for baremetal provisioning using diskimage-builder (DIB), suitable for deployment with Metal3/Ironic.
Prerequisites¶
Common Dependencies (amd64 & arm64)¶
pip3 install diskimage-builder
sudo apt update
sudo apt install -y qemu-utils
Additional Dependencies (arm64 only)¶
sudo apt install -y kpartx gdisk
Step 1: Build the Base Image¶
Create a script called create_image.sh with the following content:
#!/usr/bin/env bash
set -euo pipefail
# Ubuntu release codename
export DIB_RELEASE="noble"
export DIB_CLOUD_INIT_DATASOURCES="ConfigDrive, OpenStack"
# Output settings
OUTPUT_IMAGE_NAME="ubuntu-24.04-baremetal-final"
OUTPUT_FORMAT="qcow2"
# Elements for a UEFI-bootable, bare-metal-ready image
ELEMENTS="ubuntu vm baremetal cloud-init openssh-server \
block-device-efi bootloader grub2 \
growroot \
dracut-network dhcp-all-interfaces"
# Additional packages
PACKAGES="${PACKAGES:-} lvm2 mdadm smartmontools"
# Build the image
disk-image-create \
-o "${OUTPUT_IMAGE_NAME}.${OUTPUT_FORMAT}" \
-t "${OUTPUT_FORMAT}" \
${ELEMENTS} \
-p "${PACKAGES}" \
--checksum
Run the script:
chmod +x create_image.sh
./create_image.sh
This produces ubuntu-24.04-baremetal-final.qcow2.
Step 2: Resize ESP Partition and Fix GRUB¶
After building the base image, the EFI System Partition (ESP) may need resizing and GRUB needs to be properly configured. The script below handles both.
Create a script called finalize_image.sh:
#!/usr/bin/env bash
set -euo pipefail
############################################
# CONFIG
############################################
ORIGINAL_IMAGE="${1:-ubuntu-24.04-arm-baremetal.qcow2}"
WORK_IMAGE="working.qcow2"
RESIZED_IMAGE="resized.qcow2"
FINAL_IMAGE="ubuntu-24.04-arm-baremetal-final.qcow2"
MOUNT_DIR="/mnt/qcow2_image"
NBD_DEV="/dev/nbd0"
FORCE="${FORCE:-false}"
############################################
# CLEANUP HANDLER
############################################
cleanup() {
echo "Cleaning up..."
set +e
mountpoint -q "$MOUNT_DIR/boot/efi" && sudo umount "$MOUNT_DIR/boot/efi"
mountpoint -q "$MOUNT_DIR/dev" && sudo umount "$MOUNT_DIR/dev"
mountpoint -q "$MOUNT_DIR/proc" && sudo umount "$MOUNT_DIR/proc"
mountpoint -q "$MOUNT_DIR/sys" && sudo umount "$MOUNT_DIR/sys"
mountpoint -q "$MOUNT_DIR" && sudo umount "$MOUNT_DIR"
[ -b "$NBD_DEV" ] && sudo qemu-nbd -d "$NBD_DEV" 2>/dev/null || true
echo "Cleanup complete"
}
trap cleanup EXIT
############################################
# FORCE REBUILD (optional)
############################################
if [ "$FORCE" = "true" ]; then
echo "FORCE rebuild enabled. Removing old artifacts..."
rm -f "$WORK_IMAGE" "$RESIZED_IMAGE" "$FINAL_IMAGE"
fi
[ -f "$ORIGINAL_IMAGE" ] || { echo "Image not found: $ORIGINAL_IMAGE"; exit 1; }
############################################
# INSTALL REQUIRED TOOLS
############################################
sudo apt-get update
sudo apt-get install -y libguestfs-tools qemu-utils
############################################
# INSPECT ORIGINAL IMAGE
############################################
echo "Inspecting original image..."
virt-filesystems --long --parts --filesystems -a "$ORIGINAL_IMAGE"
qemu-img info "$ORIGINAL_IMAGE"
############################################
# AUTO-DETECT EFI + ROOT PARTITIONS
############################################
EFI_PART=$(virt-filesystems -a "$ORIGINAL_IMAGE" --long --filesystems | \
awk '$3 == "vfat" {print $1; exit}')
ROOT_PART=$(virt-filesystems -a "$ORIGINAL_IMAGE" --long --filesystems | \
awk '$3 ~ /ext4|xfs/ {print $1, $6}' | \
sort -k2 -nr | head -1 | awk '{print $1}')
[ -n "$EFI_PART" ] || { echo "EFI partition not found"; exit 1; }
[ -n "$ROOT_PART" ] || { echo "Root partition not found"; exit 1; }
echo "EFI Partition: $EFI_PART"
echo "Root Partition: $ROOT_PART"
############################################
# RESIZE + SPARSIFY
############################################
if [ -f "$FINAL_IMAGE" ]; then
echo "Final image already exists. Skipping resize & sparsify."
elif [ -f "$RESIZED_IMAGE" ]; then
echo "Resized image exists. Running sparsify only..."
virt-sparsify --compress "$RESIZED_IMAGE" "$FINAL_IMAGE"
else
cp "$ORIGINAL_IMAGE" "$WORK_IMAGE"
NEW_SIZE=5G
qemu-img create -f qcow2 "$RESIZED_IMAGE" "$NEW_SIZE"
virt-resize \
--resize-force "$EFI_PART=300M" \
--expand "$ROOT_PART" \
"$WORK_IMAGE" \
"$RESIZED_IMAGE"
virt-sparsify --compress "$RESIZED_IMAGE" "$FINAL_IMAGE"
fi
############################################
# FIX GRUB
############################################
echo "Fixing GRUB..."
sudo modprobe nbd max_part=16
sudo qemu-nbd -c "$NBD_DEV" "$FINAL_IMAGE"
sleep 2
EFI_DEV=""
ROOT_DEV=""
while read -r name fstype size; do
[[ "$fstype" == "vfat" ]] && EFI_DEV="/dev/$name"
[[ "$fstype" == "ext4" || "$fstype" == "xfs" ]] && ROOT_DEV="/dev/$name"
done < <(lsblk -ln -o NAME,FSTYPE,SIZE "$NBD_DEV")
[ -n "$EFI_DEV" ] || { echo "EFI device not found"; exit 1; }
[ -n "$ROOT_DEV" ] || { echo "Root device not found"; exit 1; }
sudo mkdir -p "$MOUNT_DIR"
sudo mount "$ROOT_DEV" "$MOUNT_DIR"
sudo mkdir -p "$MOUNT_DIR/boot/efi"
sudo mount "$EFI_DEV" "$MOUNT_DIR/boot/efi"
sudo mount --bind /dev "$MOUNT_DIR/dev"
sudo mount --bind /proc "$MOUNT_DIR/proc"
sudo mount --bind /sys "$MOUNT_DIR/sys"
# Detect image architecture
IMAGE_ARCH=$(sudo chroot "$MOUNT_DIR" uname -m || echo "")
if [[ "$IMAGE_ARCH" == "x86_64" ]]; then
GRUB_TARGET="x86_64-efi"
elif [[ "$IMAGE_ARCH" == "aarch64" || "$IMAGE_ARCH" == "arm64" ]]; then
GRUB_TARGET="arm64-efi"
else
echo "Unsupported architecture: $IMAGE_ARCH"; exit 1
fi
# Verify GRUB binaries exist
[ -d "$MOUNT_DIR/usr/lib/grub/$GRUB_TARGET" ] || \
{ echo "GRUB EFI binaries not found"; exit 1; }
# Install GRUB
sudo chroot "$MOUNT_DIR" grub-install \
--target="$GRUB_TARGET" \
--efi-directory=/boot/efi \
--bootloader-id=ubuntu \
--recheck
sudo chroot "$MOUNT_DIR" grub-install \
--target="$GRUB_TARGET" \
--efi-directory=/boot/efi \
--bootloader-id=BOOT \
--recheck
sudo chroot "$MOUNT_DIR" update-grub
echo "GRUB fixed successfully"
############################################
# GENERATE CHECKSUM
############################################
sha256sum "$FINAL_IMAGE" > "$FINAL_IMAGE.sha256"
sha256sum --check "$FINAL_IMAGE.sha256"
echo "FINAL IMAGE READY: $FINAL_IMAGE"
Run the script:
chmod +x finalize_image.sh
./finalize_image.sh ubuntu-24.04-baremetal-final.qcow2
To force a full rebuild from scratch:
FORCE=true ./finalize_image.sh ubuntu-24.04-baremetal-final.qcow2
Troubleshooting: GRUB Drops to Prompt After Deployment¶
Problem¶
After deploying the image via Metal3/Ironic, GRUB drops to a shell prompt instead of booting automatically. You have to manually enter:
configfile (hd0,gpt1)/EFI/BOOT/grub.cfg
Root Cause¶
The GRUB bootloader inside the QCOW2 image is not configured to automatically locate its grub.cfg when booted on the target hardware.
Manual Fix¶
If you need to fix this on an already-built image without rerunning the full script, you can mount and chroot into it manually.
Mount the image:
sudo modprobe nbd max_part=16
sudo qemu-nbd -p 1 -c /dev/nbd0 ubuntu-24.04-baremetal-final.qcow2
# Verify partition layout
lsblk /dev/nbd0
fdisk -l /dev/nbd0
# Mount root and EFI partitions
sudo mkdir -p /mnt/qcow2_image
sudo mount /dev/nbd0p3 /mnt/qcow2_image
sudo mkdir -p /mnt/qcow2_image/boot/efi
sudo mount /dev/nbd0p1 /mnt/qcow2_image/boot/efi
# Bind-mount host filesystems
sudo mount --bind /dev /mnt/qcow2_image/dev
sudo mount --bind /proc /mnt/qcow2_image/proc
sudo mount --bind /sys /mnt/qcow2_image/sys
# Copy DNS resolver config
sudo rm -f /mnt/qcow2_image/etc/resolv.conf
sudo cp /etc/resolv.conf /mnt/qcow2_image/etc/resolv.conf
# Enter the image
sudo chroot /mnt/qcow2_image /bin/bash
Inside the chroot — reinstall GRUB:
apt update
apt install -y grub-efi-amd64 # for x86_64 UEFI systems
grub-install --target=x86_64-efi \
--efi-directory=/boot/efi \
--bootloader-id=ubuntu \
--recheck
grub-install --target=x86_64-efi \
--efi-directory=/boot/efi \
--bootloader-id=BOOT \
--recheck
update-grub
exit
Note
For arm64 images, replace grub-efi-amd64 with grub-efi-arm64 and use --target=arm64-efi.
Unmount and disconnect:
sudo umount /mnt/qcow2_image/boot/efi
sudo umount /mnt/qcow2_image/dev
sudo umount /mnt/qcow2_image/proc
sudo umount /mnt/qcow2_image/sys
sudo umount /mnt/qcow2_image
sudo qemu-nbd -d /dev/nbd0
Generate checksum:
sha256sum ubuntu-24.04-baremetal-final.qcow2 > ubuntu-24.04-baremetal-final.qcow2.sha256
sha256sum --check ubuntu-24.04-baremetal-final.qcow2.sha256
Final Output¶
After completing the steps above, the image ubuntu-24.04-baremetal-final.qcow2 is ready for use with your Metal3/Ironic baremetal provisioning setup.