Automating Bare-Metal Provisioning with Canonical MAAS

Provisioning physical hardware typically requires mounting ISOs, navigating installation wizards, and manually setting up networks. Canonical MAAS (Metal as a Service) automates this entire process, enabling you to discover, commission, and deploy bare-metal servers using PXE, IPMI/Redfish, and cloud-init.
Deploying Region & Rack Controllers
MAAS is built on two primary components: a Region Controller (which houses the API and PostgreSQL database) and Rack Controllers (handling DHCP, TFTP/PXE, and BMC power management). Both can be installed on a central management host using snap packages.
# Install MAAS via snap
sudo snap install maas
# Initialize the Region and Rack controllers on the same node
sudo maas init --mode all
# Create the initial administrative user
sudo maas createadmin \
--username=admin \
--password=securepassword \
--email=admin@homelab.local \
--ssh-import=gh:yourgithubhandle
Note: Installing through snap ensures MAAS dependencies remain isolated. The --ssh-import flag automatically fetches your public SSH keys from GitHub and configures them for the ubuntu user on every machine provisioned by MAAS.
Configuring the Fabric and DHCP
To enable PXE booting for servers, MAAS requires control over DHCP on your provisioning subnet (referred to as a "Fabric" in MAAS). You can activate and configure DHCP directly through the CLI.
# Authenticate the CLI session
maas login admin http://localhost:5240/MAAS/api/2.0/
# Identify the VLAN ID for our provisioning subnet (e.g., 10.0.10.0/24)
maas admin subnets read
# Assume VLAN ID is 0
# Enable DHCP on the untagged VLAN, setting IP ranges
maas admin vlan update 0 0 \
dhcp_on=True \
primary_rack=node-hostname
# Define the dynamic IP range for PXE booting (enlistment/commissioning)
maas admin ipranges create \
type=dynamic \
start_ip=10.0.10.100 end_ip=10.0.10.200 \
subnet=10.0.10.0/24
Note: MAAS manages IP addresses in two distinct phases. Dynamic ranges are dedicated to temporary PXE boot environments during initial hardware discovery (enlistment) and validation (commissioning). Fully deployed machines are assigned static IPs outside of this dynamic pool.
Automated Node Provisioning via Cloud-Init
Once nodes successfully complete enlistment (PXE discovery) and commissioning (hardware auditing), they transition to a 'Ready' status. You can subsequently reserve a server that meets your criteria and deploy an operating system using a custom cloud-init script for post-installation setup.
# cloud-init.yaml
#cloud-config
packages:
- docker.io
- fail2ban
runcmd:
- systemctl enable --now docker
- usermod -aG docker ubuntu
# Allocate a node with specific constraints (e.g., min 8 cores, 32GB RAM)
NODE_ID=$(maas admin machines allocate cores=8 mem=32768 | jq -r '.system_id')
# Deploy Ubuntu 22.04 with our custom cloud-init script
maas admin machine deploy $NODE_ID \
osystem=ubuntu \
distro_series=jammy \
user_data=$(base64 -w0 cloud-init.yaml)
Note: The allocate command reserves a server matching your specified resource constraints (8 CPU cores and 32GB of RAM in this scenario). The deploy command leverages IPMI or Redfish to power up the machine, PXE boot the Ubuntu Jammy image, write the operating system to disk, run the base64-encoded cloud-init configuration upon first boot, and reboot the system.