Using Packer Builders to Keep Multi‑Cloud Images Consistent
Packer lets you build identical machine images on AWS, Azure, GCP, and VMware from one HCL file. This post shows how to use source, provisioner, and build blocks to keep images consistent, plus trade‑offs and a verification checklist.
21 Sept 2026, 08:37 UTC

Problem: Image Drift Across Clouds
When teams deploy the same application on AWS, Azure, GCP, and on‑prem VMs, they often end up with slightly different images. A patch that lands on Ubuntu 22.04 in AWS might be missing in the Azure copy, causing a test to pass on one platform and fail on another. Manual duplication of images is error‑prone and hard to audit.
Why Packer Builders Help
Packer separates the definition of a machine image from the cloud provider it runs on. The source block holds provider‑specific parameters; the build block orchestrates multiple sources and provisioners. With a single HCL file you can produce an identical image in every cloud.
Source Block Basics
The source block names the builder type (e.g., amazon-ebs for AWS, azure-arm for Azure). Inside you set the base image, region, instance size, and any provider‑specific tags.
Provisioners
Provisioners run after the instance boots. They can be shell scripts, Ansible, Chef, or PowerShell. They are the only place where you install packages or tweak configuration.
Build Block
The build section lists all sources and the shared provisioners. Packer runs each source in parallel (unless you set parallelism) and returns one artifact per source.
Concrete Example: Build a Web Server for AWS and Azure
variable "image_tag" {
type = string
default = "web-server-v1"
}
source "amazon-ebs" "aws" {
ami_id = "ami-0c55b159cbfafe1f0" # Ubuntu 22.04 LTS
region = "us-east-1"
instance_type = "t3.micro"
tags = { Name = var.image_tag }
}
source "azure-arm" "azure" {
os_type = "Linux"
vm_size = "Standard_B1s"
image_publisher = "Canonical"
image_offer = "UbuntuServer"
image_sku = "22_04-lts"
location = "EastUS"
resource_group_name = "packer-rg"
}
build {
sources = [
"source.amazon-ebs.aws",
"source.azure-arm.azure"
]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl enable nginx"
]
}
}
Run the following commands in the directory containing the file:
packer init– downloads the AWS and Azure plugins.packer validate .packer.hcl– checks syntax and missing variables.packer build .packer.hcl– creates the two images.
When the build finishes, you should see an AMI ID and an Azure image ID in the output. Verify each image by launching a small instance and checking that nginx is running.
Trade‑offs and Practical Limits
- Provisioning time: Heavy shell scripts or large package installs can make builds slow. Keep provisioning idempotent and consider using a pre‑baked base image.
- Network requirements: Packer uses SSH or WinRM to reach the instance. Ensure that the security groups or firewalls allow port 22 (Linux) or 5985/5986 (Windows) from the machine running Packer.
- Secrets handling: Never hard‑code passwords or keys in the HCL file. Use environment variables,
packer variables, or a secret manager integration.
Verification Checklist
| Check | Command / Action |
|---|---|
| Syntax | packer validate .packer.hcl |
| Plugin compatibility | packer init |
| Image metadata | Inspect the console of each provider (AMI tags, Azure image properties) |
| Runtime behaviour | Launch a test instance and run systemctl status nginx |
Conclusion
Packer’s Builder model gives you a single source of truth for machine images that can span cloud providers. By keeping the build logic in HCL and separating provider specifics into source blocks, you avoid image drift and simplify audits. Just remember to keep provisioning lean, secure your secrets, and always validate before you spin up new infrastructure.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.