Docs / Automation & IaC / How to Use Packer to Create Custom Server Images

How to Use Packer to Create Custom Server Images

By Admin · Mar 2, 2026 · Updated Apr 23, 2026 · 23 views · 3 min read

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.

Was this article helpful?