/ website / blog

build freebsd image using packer

November 28, 2023

Last time we covered booting a FreeBSD VM image using QEMU. This is great for setting up a VM by hand or just logging in to play around and experiment with the system. For production use though we want to automate configuration of the FreeBSD VM image so that we are not manually running install commands and editing configuration files, etc.

For this we use Packer from HashiCorp.

Once again these instructions assume an amd64 (x86_64) Ubuntu 22.04 host machine and using QEMU as the virtualization layer. The Linux KVM hardware accelerator can be used to improve performance.

Install Packer

The official instructions vary per operating system, but in reality all that’s needed is to download the self-contained packer executable.

➜ packer version
Packer v1.9.4

Enable rootless KVM

By default Linux disallows non-root users from making use of the KVM feature of the kernel. The implication is that all commands launching a VM would need to be run as root which is not ideal. Instead we can change the permissions to allow non-root users to use the KVM.

sudo chmod a+rw /dev/kvm

Download CI FreeBSD VM image

FreeBSD thankfully provides CI VM images which include sshd running by default. This is a huge quality of life improvement over dealing with boot commands and kickstart configuration. All we need to do is configure Packer to access the VM using SSH after booting.

Packer can be configured to download and extract images automatically but it’s more efficient to download the image and decompress it before invoking packer. Iterating on the packer config then does not re-download and re-decompress the image.

wget https://download.freebsd.org/releases/CI-IMAGES/14.0-RELEASE/amd64/Latest/FreeBSD-14.0-RELEASE-amd64-BASIC-CI.raw.xz
unxz FreeBSD-14.0-RELEASE-amd64-BASIC-CI.raw.xz

The unxz command can take several minutes to complete even on a modern machine with an NVMe drive.

Create packer config

Create a minimal packer config file freebsd.pkr.hcl

source "qemu" "freebsd14" {
  iso_urls = [
    "./FreeBSD-14.0-RELEASE-amd64-BASIC-CI.raw"
  ]
  iso_checksum = "none"
  accelerator  = "kvm"
  format       = "qcow2"
  qemuargs = [
    ["-bios", "/usr/share/ovmf/OVMF.fd"],
    ["-cpu", "host"],
  ]
  headless   = true
  disk_image = true
  efi_boot   = true
  boot_wait  = "30s"
  memory     = 2048
  cpus       = 2

  ssh_username = "root"
  ssh_password = ""

  shutdown_command = "shutdown -p now"
  shutdown_timeout = "30s"
  output_directory = "outputs"
  vm_name          = "freebsd14.qcow2"
}

build {
  sources = ["source.qemu.freebsd14"]
}

packer {
  required_plugins {
    qemu = {
      source  = "github.com/hashicorp/qemu"
      version = "~> 1"
    }
  }
}

Use packer init to install the necessary plugin(s) for QEMU.

packer init freebsd.pkr.hcl

At this point we already have enough to start building an image.

packer build freebsd.pkr.hcl

We could run the image directly using QEMU like in the previous blog post, but that isn’t the goal. What we really want to do is automatically configure the image, turning it into the useful machine it should be.

Configure the image with provisioners

As an example let’s add a setup.sh script that runs some basic commands for bootstrapping the system. We’ll also place a file into /tmp on the server just to demonstrate how.

In a directory named files create hello.txt

Locally,

➜ cat files/hello.txt
Hello from FreeBSD! :)

And also create setup.sh

#!/usr/bin/env sh

set -xeuo pipefail

# Update base image.

freebsd-update --not-running-from-cron fetch install

# Some automation defaults.

export ASSUME_ALWAYS_YES=YES
echo WITH_PKGNG=yes >> /etc/make.conf
pkg bootstrap
pkg update
pkg upgrade -y
echo 'autoboot_delay="0"' >> /boot/loader.conf

# Install packages.

pkg install bat

Now we can edit the build block of the packer config with two provisioners. The first uses the "file" provisioner to copy our files directory onto the machine under /tmp/install/. The second uses the "shell" provisioner to execute the commands in setup.sh which in our case updates the system, sets some nice automation defaults around packages, and installs the bat package.

build {
  sources = ["source.qemu.freebsd14"]

  provisioner "file" {
    destination = "/tmp/install"
    source      = "./files"
  }

  provisioner "shell" {
    scripts = [
      "./files/setup.sh",
    ]
  }
}

Ready to Run

And with that, our Packer configuration is able to build and configure the FreeBSD image automatically, and repeatedly the same way every time. If you boot into the machine you should be able to run the bat command.

packer build freebsd.pkr.hcl
qemu-system-x86_64 \
  -m 4096 \
  -smp 2 \
  -cpu host \
  -bios /usr/share/ovmf/OVMF.fd \
  -serial mon:stdio -nographic \
  -drive file=./outputs/freebsd14.qcow2 \
  -enable-kvm
root@freebsd:~ # bat /tmp/install/hello.txt
───────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: /tmp/install/hello.txt
───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ Hello from FreeBSD! :)
───────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
root@freebsd:~ #

gl;hf!

➡️ related posts in the virtualization series ...