Custom machine setup

During machine enlistment, deployment, commissioning and machine installation, MAAS sends Tempita-derived configuration files to the cloud-init process running on the target machine. MAAS refers to this process as preseeding.These preseed files are used to configure a machine’s ephemeral and installation environments and can be modified or augmented to a custom machine configuration.

Quick questions you may have:

Customisation in MAAS happens in two ways:

  1. Curtin, a preseeding system similar to Kickstart or d-i (Debian Installer), applies customisations during operating system (OS) image installation. MAAS performs these changes on deployment, during OS installation, but before the machine reboots into the installed OS. Curtin customisations are perfect for administrators who want their deployments to have identical setups all the time, every time. This blog post contains an excellent high-level overview of custom MAAS installs using Curtin.
  2. Cloud-init, a system for setting up machines immediately after instantiation. cloud-init applies customisations after the first boot, when MAAS changes a machine’s status to ‘Deployed.’ Customisations are per-instances, meaning that user-supplied scripts must be re-specified on redeployment. Cloud-init customisations are the best way for MAAS users to customise their deployments, similar to how the various cloud services prepare VMs when launching instances.



The Tempita template files are found within the /var/snap/maas/current/preseeds/ for the snap and /etc/maas/preseeds/ directory for the debian package on the region controller. Each template uses a filename prefix that corresponds to a particular phase of MAAS machine deployment:

Phase Filename prefix
1. Enlistment enlist
2. Commissioning commissioning
3. Installation curtin (Curtin)

Additionally, the template for each phase typically consists of two files. The first is a higher-level file that often contains little more than a URL or a link to further credentials, while a second file contains the executable logic.

The enlist template, for example, contains only minimal variables, whereas enlist_userdata includes both user variables and initialisation logic.

Tempita’s inheritance mechanism is the reverse of what you might expect. Inherited files, such as enlist_userdata, become the new template which can then reference variables from the higher-level file, such as enlist.

Template naming

MAAS interprets templates in lexical order by their filename. This order allows for base configuration options and parameters to be overridden based on a combination of operating system, architecture, sub-architecture, release, and machine name.

Some earlier versions of MAAS only support Ubuntu. If the machine operating system is Ubuntu, then filenames without {os} will also be tried, to maintain backward compatibility.

Consequently, template files are interpreted in the following order:

  1. {prefix}_{os}_{node_arch}_{node_subarch}_{release}_{node_name} or {prefix}_{node_arch}_{node_subarch}_{release}_{node_name}

  2. {prefix}_{os}_{node_arch}_{node_subarch}_{release} or {prefix}_{node_arch}_{node_subarch}_{release}

  3. {prefix}_{os}_{node_arch}_{node_subarch} or {prefix}_{node_arch}_{node_subarch}

  4. {prefix}_{os}_{node_arch} or {prefix}_{node_arch}

  5. {prefix}_{os}

  6. {prefix}

  7. generic

The machine needs to be the machine name, as shown in the web UI URL.

The prefix can be either enlist, enlist_userdata, commissioning, curtin, curtin_userdata or preseed_master. Alternatively, you can omit the prefix and the following underscore.

For example, to create a generic configuration template for Ubuntu 16.04 Xenial running on an x64 architecture, the file would need to be called ubuntu_amd64_generic_xenial_node.

To create the equivalent template for curtin_userdata, the file would be called curtin_userdata_ubuntu_amd64_generic_xenial_node.

Any file targetting a specific machine will replace the values and configuration held within any generic files. If those values are needed, you will need to copy these generic template values into your new file.


You can customise the Curtin installation by either editing the existing curtin_userdata template or by adding a custom file as described above.

Curtin provides hooks to execute custom code before and after installation takes place. These hooks are named early and late respectively, and they can both be overridden to execute the Curtin configuration in the ephemeral environment. Additionally, the late hook can be used to execute a configuration for a machine being installed, a state known as in-target.

Curtin commands look like this:

foo: ["command", "--command-arg", "command-arg-value"]

Each component of the given command makes up an item in an array. Note, however, that the following won’t work:

foo: ["sh", "-c", "/bin/echo", "foobar"]

This syntax won’t work because the value of sh's -c argument is itself an entire command. The correct way to express this is:

foo: ["sh", "-c", "/bin/echo foobar"]

The following is an example of an early command that will run before the installation takes place in the ephemeral environment. The command pings an external machine to signal that the installation is about to start:

  signal: ["wget", "--no-proxy", "", "--post-data", "system_id=&signal=starting_install", "-O", "/dev/null"]

The following is an example of two late commands that run after installation is complete. Both run in-target, on the machine being installed.

The first command adds a PPA to the machine. The second command creates a file containing the machine’s system ID:

  add_repo: ["curtin", "in-target", "--", "add-apt-repository", "-y", "ppa:my/ppa"]
  custom: ["curtin", "in-target", "--", "sh", "-c", "/bin/echo -en 'Installed ' > /tmp/maas_system_id"]


Using cloud-init to customise a machine after deployment is relatively easy. If you’re not familiar with the MAAS command-line interface (CLI), start by reviewing the MAAS CLI page.

After you’re logged in, use the following command to deploy a machine with a custom script you’ve written:

maas $PROFILE machine deploy $SYSTEM_ID user_data=<base-64-encoded-script>
  • $PROFILE: Your MAAS login. E.g. admin
  • $SYSTEM_ID: The machine’s system ID.
  • <base-64-encoded-script>: A base-64 encoded copy of your customisation script. See below for an example.


Suppose you would like to import an SSH key immediately after your machine deployment. You might use this script, called

echo === $date ===
ssh-import-id foobar_user
) | tee /ssh-key-import.log

This script echos the date in addition to the output of the ssh-import-key command. It also adds that output to a file, /ssh-key-import.log.

Base-64 encoding is required because the MAAS command-line interacts with the MAAS API, and base-64 encoding allows MAAS to send the script inside a POST HTTP command.

Use the base64 command to output a base-64 encoded version of your script:

base64 -w0 ./

Putting it together:

maas $PROFILE machine deploy $SYSTEM_ID user_data=$(base64 -w0 ./

After MAAS deploys the machine, you’ll find /ssh-key-import.log on the machine you deployed.

Customising cloud-init with the UI (v2.9++)

It’s easy to customize cloud-init via the web UI. When you’ve selected a machine and choose ‘Take action >> Deploy,’ you’ll be presented with the following screen:

Select a viable release (in this case, “Ubuntu 18.04…”) and check the box labeled “Cloud-init user-data…”:

Paste the desired script directly into the box, and select “Start deployment for machine.” For example, to write a file immediately after your machine deployment, you could paste this script:

  - path: /test
    content: hello

This script simply writes hello to a the file /test.

No script validation of any kind is provided with this capability. You will need to test and debug your own cloud-init scripts.

maas $PROFILE machine deploy $SYSTEM_ID user_data=$(base64 ./
should be

There’s an error in the above as of march 18th 2020, the base64 command is incorrect and won’t work for scripts over a certain length, you need to use “base64 -w0” which disables word wrap which without it screws up cloud-init when the script is long enough.

I wrote a little wrapper where we use build snippets that do certain things (pair to puppet, install ZFS, do X_Y_Z) and a pair of scripts that list and assembles them into a usable sequence

The important script is called “assemble_snippets” and it is really simple bash



if [ "$#" -eq 0 ]; then
  echo "Invalid arguments,  need to specify at least one or more snippets"

# Validate snips exist
for SNIP in $(echo $*) ; do 
  test -f "${SNIPDIR}/${SNIP}.snippet" || { echo "Snippet ${SNIPDIR}/${SNIP}.snippet doesn't exist" ; exit 1; }

echo "#!/bin/bash"
for SNIP in $(echo $*) ; do 
  echo "#"
  echo "# Snippet ${snipcount}"
  echo "#"
  cat "${SNIPDIR}/${SNIP}.snippet"
  echo ""
) |base64 -w0

and snippets look like this:

# SUMMARY: Does task X, Y and Z
# Detailed explanation of what this does...
START=$(date +%s)
Insert steps here
END=$(date +%s)
ETIME=$(date -d@$ELAPSED -u +%H:%M:%S)
echo "XYZ snippet completed in ${ETIME}"
) |tee /root/snippet_x_y_z.log

In one of our maas testing scripts for spinning up a certain type of machine we call maas machine deploy while executing assemble_snippets in a subshell and feeding into the user_data field as shown in the following example.

DEPLOYED_IP=$(maas ${PROFILE} machine deploy ${SYSTEM_ID} distro_series='xenial-maas-2020-02-28' user_data=$(assemble_snippets fix_aptproxy serial_console zfs pair_to_puppet readymsg) | jq '. .ip_addresses[]' | tr -d '"')
echo "The machine should come up with ${DEPLOYED_IP}"

@dandruczyk, thanks. fixed in doc.

would you consider dressing up your snippet comments above, and post them to discourse?

thanks again.

I cannot customize images anymore on a new maas installation. tells me to change files in /etc/maas/preseeds but there’s no /etc/maas folder anymore. Files inside the snap directory /snap/… I cannot change. How to proceed?

1 Like

Updated the instructions to point at the right directory for the snap, see also bug 1891319

Thanks, fixed the instructions to account for -w0

This should say “cloud-init applies customisations after the first boot”, not “Curtin applies customisations after the first boot”

1 Like

thanks, @axino. fixed.

Paste the desired script directly into the box, and select “Start deployment for machine.” For example, to import an SSH key immediately after your machine deployment, you could paste this script:

This section is misleading given the actual script example. The example script does not import any ssh-keys, but rather simply writes “hello” to a file. Maybe either supply a corresponding example script or change the sentence to better reflect the example?

1 Like

@azocolo, took a while, but it should be fixed now. thanks!

1 Like