How to Use Packer to Create Custom Server Images
Packer by HashiCorp automates the creation of machine images from a single configuration. Instead of provisioning each Breeze instance from scratch, you build a golden image once and launch pre-configured instances in seconds.
Installing Packer
Install Packer on your build machine:
# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install -y packer
packer --version
Writing a Packer Template
Packer uses HCL2 templates. Create a breeze-image.pkr.hcl file:
packer {
required_plugins {
qemu = {
source = "github.com/hashicorp/qemu"
version = ">= 1.0.0"
}
}
}
variable "ssh_username" {
type = string
default = "ubuntu"
}
source "qemu" "breeze-base" {
iso_url = "https://releases.ubuntu.com/24.04/ubuntu-24.04-live-server-amd64.iso"
iso_checksum = "sha256:abcdef1234567890..."
output_directory = "output-breeze-base"
vm_name = "breeze-base.qcow2"
disk_size = "20G"
memory = 2048
cpus = 2
format = "qcow2"
ssh_username = var.ssh_username
ssh_password = "packer"
ssh_timeout = "20m"
shutdown_command = "sudo shutdown -P now"
}
build {
sources = ["source.qemu.breeze-base"]
provisioner "shell" {
inline = [
"sudo apt-get update -y",
"sudo apt-get upgrade -y",
"sudo apt-get install -y nginx curl wget ufw fail2ban htop",
"sudo systemctl enable nginx",
"sudo systemctl enable fail2ban"
]
}
provisioner "file" {
source = "files/nginx-default.conf"
destination = "/tmp/nginx-default.conf"
}
provisioner "shell" {
inline = [
"sudo mv /tmp/nginx-default.conf /etc/nginx/sites-available/default",
"sudo ufw allow 22/tcp",
"sudo ufw allow 80/tcp",
"sudo ufw allow 443/tcp"
]
}
provisioner "shell" {
inline = [
"sudo apt-get clean",
"sudo rm -rf /var/lib/apt/lists/*",
"sudo rm -rf /tmp/*",
"sudo cloud-init clean --logs"
]
}
}
Building the Image
Validate and build your image:
# Initialize plugins
packer init breeze-image.pkr.hcl
# Validate the template
packer validate breeze-image.pkr.hcl
# Build the image
packer build breeze-image.pkr.hcl
# Build with variable overrides
packer build -var 'ssh_username=admin' breeze-image.pkr.hcl
Using Provisioners
Packer supports multiple provisioners to configure images:
- Shell — run inline commands or scripts
- File — upload files and configuration
- Ansible — apply Ansible playbooks for complex configuration
- Chef/Puppet — use existing configuration management tools
provisioner "ansible" {
playbook_file = "ansible/playbook.yml"
extra_arguments = [
"--extra-vars", "env=production"
]
}
Image Pipeline Best Practices
- Clean up before finishing — remove caches, temp files, and logs for smaller images
- Use base image layers — build a base image first, then create specialized images on top
- Automate builds — trigger Packer builds from CI/CD on template changes
- Version images — tag images with build dates and Git commit hashes
- Test before deploying — launch a test Breeze instance from the new image and run smoke tests
- Run cloud-init clean — ensures cloud-init runs fresh on instances launched from the image
Packer-built images dramatically reduce Breeze instance boot times and ensure every server starts from a known-good, hardened baseline.