Packer can build multiple images simultaneously, but it’s not as simple as just throwing more builders at it — the real trick is understanding how Packer multiplexes connections and manages resources under the hood.
Let’s see this in action. Imagine you have a Packer template with two builders, say, one for AWS AMIs and another for VMware vSphere templates.
{
"builders": [
{
"type": "amazon-ebs",
"region": "us-east-1",
"ami_name": "my-aws-image-{{timestamp}}",
"source_ami": "ami-0c55b159cbfafe1f0",
"instance_type": "t3.micro",
"ssh_username": "ec2-user"
},
{
"type": "vmware-iso",
"iso_url": "http://example.com/centos.iso",
"iso_checksum": "sha256:abcdef1234567890",
"vm_name": "my-vmware-template-{{timestamp}}",
"disk_size": "40960",
"cpus": 2,
"memory": 4096,
"ssh_username": "root",
"ssh_password": "password123"
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"sudo yum update -y",
"sudo yum install -y nginx"
]
}
]
}
When you run packer build your_template.json, Packer doesn’t just spin up two independent build processes. It orchestrates them. For the AWS builder, it’s launching EC2 instances, provisioning them, and creating AMIs. For the VMware builder, it’s deploying VMs from an ISO, configuring them, and exporting them as templates. The "parallelism" here isn’t about running separate packer commands; it’s about Packer managing multiple, distinct build jobs concurrently within a single invocation.
The core problem Packer solves is the tediousness of manually creating machine images for different cloud providers or virtualization platforms. Instead of logging into AWS, launching an instance, running ami-builder, then switching to vSphere, deploying a VM, and exporting a template, Packer automates this entire workflow. It takes a declarative configuration and turns it into consistent, repeatable machine images.
Internally, Packer uses Goroutines for concurrency. Each builder type has its own set of Goroutines responsible for its specific lifecycle: launching resources, waiting for SSH to become available, executing provisioners, and finally creating the artifact. When you have multiple builders, Packer dispatches these tasks across multiple Goroutines. This allows it to, for example, wait for an EC2 instance to boot and become SSH-able while simultaneously deploying a VM in vSphere. The packer build command is a single process, but it’s managing many independent, concurrent operations.
The parallel option in Packer’s top-level configuration (or via the -parallel=false flag) controls this behavior. By default, parallel is true. If you set parallel: false, Packer will build builders sequentially. This is useful for debugging or when you have dependencies between builders (though Packer isn’t designed for complex inter-builder dependencies; it’s better to have independent builder configurations).
When you run multiple builders in parallel, Packer needs to manage network connections. For SSH, it will open multiple connections concurrently. If you’re hitting rate limits on your SSH server or have network congestion, this is where you’ll see issues. Packer multiplexes these connections through its internal worker pool. You can control the maximum number of concurrent builds using the PACKER_MAX_CONCURRENT_BUILDS environment variable. The default is usually sufficient, but if you have many builders or your infrastructure is constrained, you might need to tune this. For instance, export PACKER_MAX_CONCURRENT_BUILDS=4 will limit Packer to four concurrent builder operations.
The most surprising aspect of Packer’s parallel builds is how it handles shared resources, or rather, how it doesn’t inherently. Each builder is largely independent. If you’re building an AMI and a vSphere template, Packer won’t automatically share a downloaded ISO or a script between them. Each builder’s configuration must be self-contained, including any artifacts it needs to download or reference. This isolation is a feature, ensuring that one build failing doesn’t cascade and impact another, but it also means you might repeat downloads or configurations if not careful. For example, if both builders need a specific package installed, you’ll define that installation within each builder’s provisioners section, not expect a shared provisioning step.
The next concept you’ll want to explore is how Packer handles state and artifact management across these parallel builds, especially when dealing with dependencies or wanting to use outputs from one build in another.