Build a basic image

These instructions describe how to build a minimal classic Ubuntu 24.04 AMD64 server image with Imagecraft.

Prerequisites

  • AMD64 machine with Ubuntu 24.04

  • snapd installed (see Install snap on Ubuntu)

  • 15GiB or more disk space to process the build and hold the resulting image

Note

Following these instructions will build an AMD64 image on an AMD64 machine. Building on another architecture would need several modifications not described on this page.

Install Imagecraft

Imagecraft is available as a snap on the latest/beta channel in the Snap Store. Install it with:

sudo snap install imagecraft --channel=beta --classic

Verify that Imagecraft is properly installed:

imagecraft

Caution

Imagecraft mounts important (/dev, /sys, etc.) system directories from the building environment. When running in destructive mode, a invalid project file leading to a failure of Imagecraft could damage the system.

Prepare the configuration

Project file

The name of the project file, imagecraft.yaml, is important because Imagecraft uses it automatically. Save the following content as imagecraft.yaml:

imagecraft.yaml
imagecraft.yaml
name: ubuntu-minimal
version: "0.1"
base: bare
build-base: [email protected]

platforms:
  amd64:

volumes:
  pc:
    schema: gpt
    structure:
      - name: efi
        type: C12A7328-F81F-11D2-BA4B-00A0C93EC93B
        filesystem: vfat
        role: system-boot
        filesystem-label: UEFI
        size: 256M
      - name: rootfs
        type: 0FC63DAF-8483-4772-8E79-3D69D8477DE4
        filesystem: ext4
        filesystem-label: writable
        role: system-data
        size: 5G

filesystems:
  default:
    - mount: "/"
      device: "(volume/pc/rootfs)"
    - mount: "/boot/efi"
      device: "(volume/pc/efi)"

parts:
  # build a simple rootfs
  rootfs:
    plugin: nil
    build-packages: ["mmdebstrap"]
    overlay-script: |
      mmdebstrap --arch $CRAFT_ARCH_BUILD_FOR \
       --mode=sudo \
       --format=dir \
       --variant=minbase \
       --include=apt \
       noble \
       $CRAFT_OVERLAY/ \
       http://archive.ubuntu.com/ubuntu/
       mkdir $CRAFT_OVERLAY/boot/efi

  # add packages using chroot and apt to have maintainer scripts properly run
  packages:
    plugin: nil
    after: [rootfs]
    overlay-script: |
      mkdir -p $CRAFT_OVERLAY/dev $CRAFT_OVERLAY/sys $CRAFT_OVERLAY/proc
      mount -t devtmpfs devtmpfs-build $CRAFT_OVERLAY/dev
      mount -t devpts devpts-build -o nodev,nosuid $CRAFT_OVERLAY/dev/pts
      mount -t sysfs sysfs-build $CRAFT_OVERLAY/sys
      mount -t proc proc-build $CRAFT_OVERLAY/proc
      mount --bind /run $CRAFT_OVERLAY/run

      DEBIAN_FRONTEND=noninteractive chroot $CRAFT_OVERLAY apt update
      DEBIAN_FRONTEND=noninteractive chroot $CRAFT_OVERLAY apt install --assume-yes --quiet \
        --option=Dpkg::options::=--force-unsafe-io --option=Dpkg::Options::=--force-confold \
        ubuntu-server-minimal grub2-common grub-pc shim-signed linux-image-generic

      mount --make-rprivate $CRAFT_OVERLAY/run
      umount --recursive $CRAFT_OVERLAY/run
      mount --make-rprivate $CRAFT_OVERLAY/sys
      umount --recursive $CRAFT_OVERLAY/sys
      mount --make-rprivate $CRAFT_OVERLAY/proc
      umount --recursive $CRAFT_OVERLAY/proc
      mount --make-rprivate $CRAFT_OVERLAY/dev/pts
      umount --recursive $CRAFT_OVERLAY/dev/pts
      mount --make-rprivate $CRAFT_OVERLAY/dev
      umount --recursive $CRAFT_OVERLAY/dev
      rm $CRAFT_OVERLAY/etc/cloud/cloud.cfg.d/90_dpkg.cfg

  # prepare snaps
  snaps:
    plugin: nil
    after: [packages]
    overlay-script: |
      snap prepare-image --classic --arch=amd64 \
        --snap snapd --snap core24 "" $CRAFT_OVERLAY

  # fix fstab and organize content to the partition
  fstab:
    plugin: nil
    after: [snaps]
    overlay-script: |
      cat << EOF > $CRAFT_OVERLAY/etc/fstab
      LABEL=writable	/	ext4	discard,errors=remount-ro	0	1
      LABEL=UEFI	/boot/efi	vfat	umask=0077	0	1
      EOF

  cloud-init:
    plugin: dump
    source: cloud-init/

Cloud-init configuration

Prepare needed directories:

mkdir -p cloud-init/var/lib/cloud/seed/nocloud
mkdir -p cloud-init/etc/cloud/cloud.cfg.d/

Write the following files in the cloud-init directory:

  • cloud-init/var/lib/cloud/seed/nocloud/meta-data

    dsmode: local
    instance_id: ubuntu-server
    
  • cloud-init/var/lib/cloud/seed/nocloud/user-data

    #cloud-config
    chpasswd:
      expire: true
      users:
        - name: ubuntu
          password: ubuntu
          type: text
    
  • cloud-init/etc/cloud/cloud.cfg.d/90_dpkg.cfg

    # to update this file, run dpkg-reconfigure cloud-init
    datasource_list: [ NoCloud ]
    

Pack the image

The packing can be run in two different environments:

  • In a multipass VM:

    sudo snap set imagecraft provider=multipass
    imagecraft --verbosity debug pack
    
  • On the local machine, with destructive mode. In this case the machine must be of the series of the build-base declared in the imagecraft.yaml file.

    sudo imagecraft --verbosity debug pack --destructive-mode
    

The resulting image file, pc.img, will be deposited in the current directory.

Note

Without any specific option imagecraft will rely by default on LXD to build the image. However this mode of operation is not working yet.

Run the image

Finally, test your new image with QEMU.

First, install QEMU and the Open Virtual Machine Firmware UEFI firmware for 64-bit x86 virtual machines:

sudo apt install ovmf qemu-system-x86

Then, copy the UEFI variables to a temporary directory:

cp /usr/share/OVMF/OVMF_VARS_4M.fd /tmp/OVMF_VARS_4M.fd

Boot the resulting image with QEMU:

qemu-system-x86_64 \
-accel kvm \
-m 4G \
-cpu host \
-smp 8 \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,file=/tmp/OVMF_VARS_4M.fd \
-drive file=pc.img,format=raw,index=0,media=disk

The image should boot and give access to a shell.