Builders and Sources¶
A builder is the plugin that actually launches infrastructure, runs provisioners, and captures the resulting image. In HCL2, you declare builders inside source blocks and reference them in build blocks.
Source Block Anatomy¶
source "amazon-ebs" "ubuntu" {
ami_name = "my-ubuntu-${formatdate("YYYYMMDD", timestamp())}"
instance_type = "t3.micro"
region = "us-east-1"
source_ami = "ami-0abc123"
ssh_username = "ubuntu"
}
- First label (
amazon-ebs): builder type - Second label (
ubuntu): local name for referencing - Body: builder-specific arguments
Reference in a build:
build {
sources = ["source.amazon-ebs.ubuntu"]
}
Common AWS Builders¶
amazon-ebs (most common)¶
Launches an EC2 instance, provisions it, stops it, snapshots the root volume, registers an AMI.
Key arguments:
ami_name: final AMI name; must be unique in the accountsource_amiorsource_ami_filter: which image to start frominstance_type: EC2 instance typeregion: primary build regionami_regions: list of regions to copy AMI to after buildssh_username: typicallyubuntu,ec2-user,adminvpc_id,subnet_id,security_group_id: network config (use defaults or pin)tags: tags applied to AMIrun_tags: tags applied to builder instanceencrypt_boot: encrypt resulting AMI snapshotkms_key_id: KMS key for encryption
amazon-chroot¶
Builds an AMI in place on an already-running EC2 instance. Creates an EBS volume, mounts it, chroots in, provisions, snapshots. No new instance launched.
Use when: - Build host is already EC2 with required IAM - Build speed is critical (can be 3x to 5x faster) - Provisioners do not require networking differences between chroot and host
amazon-ebssurrogate¶
Builds an AMI from a detached root volume. Advanced; useful for creating AMIs with custom root partitions or when starting from non-AMI sources.
amazon-instance¶
For instance-store backed AMIs. Rarely used today; EBS-backed is standard.
source_ami_filter¶
Avoid hardcoding AMI IDs. Use a filter to resolve the latest matching AMI at build time:
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"] # Canonical
}
Owners by name: - Canonical (Ubuntu): 099720109477 - Amazon (Amazon Linux): amazon - Red Hat: 309956199498
Multi-Region Distribution¶
Build once in region, copy to others:
source "amazon-ebs" "ubuntu" {
region = "us-east-1"
ami_regions = ["us-west-2", "eu-west-1"]
# ...
}
Packer copies the snapshot and registers an AMI in each listed region. All share the same name.
Azure Builders¶
azure-arm¶
Builds a managed image or a Shared Image Gallery version.
source "azure-arm" "ubuntu" {
subscription_id = var.subscription_id
tenant_id = var.tenant_id
client_id = var.client_id
client_secret = var.client_secret
managed_image_name = "ubuntu-${formatdate("YYYYMMDD", timestamp())}"
managed_image_resource_group_name = "images"
location = "East US"
vm_size = "Standard_DS2_v2"
os_type = "Linux"
image_publisher = "Canonical"
image_offer = "0001-com-ubuntu-server-jammy"
image_sku = "22_04-lts-gen2"
}
Authenticate via service principal (above) or Managed Identity if running on Azure VM.
GCP Builder¶
googlecompute¶
source "googlecompute" "ubuntu" {
project_id = "my-project"
source_image_family = "ubuntu-2204-lts"
zone = "us-central1-a"
image_name = "ubuntu-${formatdate("YYYYMMDD", timestamp())}"
machine_type = "e2-medium"
ssh_username = "packer"
}
Auth: GOOGLE_APPLICATION_CREDENTIALS env var pointing to service account JSON, or workload identity if running on GCP.
Docker Builder¶
source "docker" "ubuntu" {
image = "ubuntu:22.04"
commit = true # or `discard = true` for no output
changes = [
"EXPOSE 8080",
"ENTRYPOINT [\"/usr/bin/myapp\"]",
"USER myapp",
]
}
commit = true: image is committed, usable by docker-tag/docker-pushexport_path = "out.tar": alternatively export as tarballchanges: Dockerfile-like metadata applied at commit
VMware and vSphere Builders¶
vsphere-iso¶
Builds from ISO on vSphere:
source "vsphere-iso" "ubuntu" {
vcenter_server = "vcenter.example.com"
username = var.vsphere_user
password = var.vsphere_pass
datacenter = "dc1"
cluster = "cluster1"
datastore = "datastore1"
iso_url = "https://releases.ubuntu.com/22.04/ubuntu-22.04.3-live-server-amd64.iso"
iso_checksum = "sha256:..."
vm_name = "ubuntu-template"
boot_command = ["..."]
ssh_username = "ubuntu"
}
Typically paired with vsphere-template post-processor to convert the resulting VM to a template.
Other Builders¶
qemu: KVM-based; produces qcow2 imagesvirtualbox-iso: VirtualBox; produces OVA/OVFhyperv-iso: Hyper-V on Windowsoracle-oci: Oracle Cloudalicloud-ecs: Alibaba Cloudnull: no instance, provisioner-only workflows
The null Builder¶
source "null" "example" {
communicator = "none"
}
build {
sources = ["source.null.example"]
provisioner "shell-local" {
inline = ["echo 'Runs on host only'"]
}
}
No instance is launched. Useful for shell-local pipelines, data aggregation, or testing provisioner logic.
Choosing Between Builders¶
| Use case | Builder |
|---|---|
| Standard AMI build | amazon-ebs |
| Super-fast in-place AMI | amazon-chroot |
| Azure image | azure-arm |
| GCP image | googlecompute |
| Docker container | docker |
| VMware template | vsphere-iso |
| On-prem VM | qemu, virtualbox-iso |
| Provisioner-only workflow | null |
Builder-Level Settings¶
Most builders share common fields:
communicator:ssh,winrm, ornonessh_private_key_file,ssh_password,ssh_keypair_namepause_before_connecting: delay before first connectionuser_data/user_data_file: cloud-init data for first boottemporary_key_pair_type,temporary_key_pair_bits: ephemeral key generation
Communicators¶
- ssh: Linux/BSD builders, default
- winrm: Windows builders
- none: null builder, no provisioners use remote connection
For Windows builds you typically set communicator = "winrm", winrm_username = "Administrator", plus user-data to enable WinRM.
Temporary Resources¶
Packer creates and cleans up:
- Temporary key pair (SSH)
- Temporary security group (allowing SSH from Packer's IP)
- Temporary subnet assignments if not specified
- IAM instance profile if configured
If Packer crashes, these may leak. Check your cloud account and tag resources with packer to find orphans.
Multiple Sources in One Build¶
source "amazon-ebs" "base" { ... }
source "amazon-ebs" "web" { ... }
build {
name = "multi"
sources = [
"source.amazon-ebs.base",
"source.amazon-ebs.web",
]
provisioner "shell" {
# runs against both sources in parallel
}
}
Use only or except to filter at runtime:
packer build -only='amazon-ebs.web' .
packer build -except='amazon-ebs.base' .
Dynamic Source Configuration¶
Use locals and dynamic blocks to DRY up multi-region or multi-variant builds:
locals {
regions = ["us-east-1", "us-west-2"]
}
source "amazon-ebs" "ubuntu" {
region = "us-east-1"
ami_regions = local.regions
# ...
}
For truly different sources (different OS base images), declare multiple sources rather than trying to parameterize one.
Exam-Ready Checklist¶
- Can author an
amazon-ebssource withsource_ami_filter - Know when to use
amazon-chrootvsamazon-ebs - Understand
ami_regionsfor multi-region distribution - Can declare Azure, GCP, Docker sources
- Know what the
nullbuilder is for - Can filter builds with
onlyandexcept