Austin Shafer

home contact

Introducing Packmule

automatically create customized FreeBSD images in minutes

port - source

Packmule is a tool I wrote to customize FreeBSD images without recompiling anything. It can:

  • Add individual files and packages to an existing image.
  • Turn a regular image into a unattended image. (automatically installs without any user input)
  • Add packages or files to the Live CD itself.
  • Operate on ISO or Memstick images.

It's also perfect for organizing large scale VM or server installations. Let's assume you are the proud owner of 30 servers, each of which needs to be set up with an identical installation of FreeBSD containing the same packages and configuration files. It's relatively easy to set up a tool like Ansible to manage each server, but you are not looking forward to manually installing Ansible's base dependencies 30 times. What you really need is a unattended installation image that installs FreeBSD and Ansible's dependencies for you.


Packmule is now a part of the ports tree, meaning installing is as simple as:

pkg install packmule

Check out man packmule once you've installed for the complete rundown.

Packmule Basics

Packmule packs a FreeBSD installation image with custom content, making it useful for generating install images with all your normal utilities included. The added packages are installed along with the regular contents of FreeBSD. Generated images have the form FreeBSD-*-packed.iso. Installation can proceed as normal with the packed software showing up on the newly installed system alongside the FreeBSD base.

Simply burn the FreeBSD-*-packed.iso image to a CD and install it as you would with a regular FreeBSD installation:

Installation screenshot

As shown above, the packmule-pkgs distribution archive is being installed alongside the base system and kernel. When complete, the machine will appear as though the user logged in and manually installed the software.


Packmule is configured using the YAML configuration language. The default configuration file is ./packmule.yml but it is recommended to specify a specific file using the -y argument. The three main sections are as follows:


This section holds names of packages to be installed through the pkg(8) tool.


Individual files or directories to be copied into the new image. Values are separated by :, with the value on the left being the source file and the value on the right being the destination. ( /path/to/src : /install/location)


Same as PKGS, but installs packages onto the live cd image instead of the host. This provides an easy way to add software to a live usb image, which is really useful for testing on laptops where you can't install a new operating system. Or for adding vim to a live usb because you can't stand vi.


Same as LIVE_CD_PKGS, but installs individual files instead of packages.


A path to an installerconfig script to be used for an unattended installation. More information about unattended installations and installerconfigs can be found in bsdinstall(8)

Here is an example configuration (config.yml):

# example config.yml

- perl5-5.24.3
- nethack36
- gnome3

# Format: /host/location : /install/location
/root/.shrc : /root/.shrc
/etc/hosts : /etc/hosts

INSTALLERCONFIG : ~user/installerconfig

The above example is much smaller than what is useful. This configuration will install perl, nethack, and gnome3 in the installation image. It also copies individual files such as resolv.conf and hosts. This is useful for installing custom configuration files automatically on a system. An installation image can use this method to install arbitrary configuration files.


Here's a simple usage example to walk you through using Packmule. To perform this task you will need a FreeBSD image, preferably a 12.0-RELEASE one. For simplicity, I assume all the files mentioned will be in your home directory. Let's begin with our initial problem of automatically installing Ansible.

Unattended installation for Ansible management

Although it is agentless, Ansible requires a few basic things like a python interpreter and sudo. It also requires a user with access to the wheel group (gid 0). The user is added through the installerconfig.

First we need to create an installerconfig file to tell the FreeBSD installer what actions to perform. More information about unattended installer configurations can be found in the bsdinstall(8) manpage. Our configuration should look something like:

# ~/installerconfig
# Configure the partition to install to and select distributions to add

# vtbd0 is the bhyve default. You will have to pick an appropriate value for
# your machine

DISTRIBUTIONS="kernel.txz base.txz packmule-pkgs.txz"`

#! /bin/sh

# -- This portion of the installerconfig is run after installation --

# set a new root user password
echo "example-pass" | pw usermod root -h 0

# add a user for ansible to manage
echo "user-pass" | pw useradd -n example -u 1001 -g 0 -h 0

The YAML configuration file for this example is very simple:

# config.yml

# select python 3.6 for ansible
- python36
- sudo

# select our installerconfig from above
INSTALLERCONFIG : ~/installerconfig

The following command puts it all together:

$ cd ~/
$ packmule -y ~/config.yml FreeBSD-12.0-RELEASE-amd64-dvd1.iso

The resulting image will have the name FreeBSD-12.0-RELEASE-amd64-dvd1-packed.iso. Boot the image on whatever target machine you want FreeBSD on, at it will take care of the rest. Don't accidentally install on the wrong machine, as the installerconfig you wrote will most likely just overwrite the disk.

Going Forward

Adding support for Memstick images and live CD upgrades opened new doors for the tool. I wrote the features so I could replace the kernel on a live USB stick in order to test some driver fixes on a friend's laptop without overwriting his OS.

If you find the tool useful, and happen to be a port committer, please consider pushing the bugzilla issue along!

Packmule gets updated whenever I need a new feature, which is not very often. Currently I have no features planned, but if you have any suggestions or pull requests please comment on the GitHub repository!