Packer plugins aren’t just extensions; they’re fully integrated components that can fundamentally alter Packer’s behavior, often in ways that are more deeply embedded than you’d expect.
Let’s see how this plays out with a common workflow: building an AWS AMI using the amazon-ebs builder and needing to run some custom provisioning after the AMI is created but before Packer considers the build complete. This is a perfect scenario for a post-processor plugin.
Imagine you want to copy the newly created AMI to a different AWS region for disaster recovery. The aws-region-copy post-processor plugin is ideal for this.
Here’s a Packer template (template.pkr.hcl) that uses it:
packer {
required_plugins {
amazon = {
version = ">= 1.0.0"
source = "github.com/hashicorp/amazon"
}
aws-region-copy = {
version = ">= 0.1.0"
source = "github.com/hashicorp/aws-region-copy"
}
}
}
source "amazon-ebs" "example" {
region = "us-east-1"
ami_name = "packer-example-{{timestamp}}"
instance_type = "t3.micro"
ssh_username = "ec2-user"
source_ami_filter {
filters = {
Name = "amzn2-ami-hvm-*-x86_64-gp2"
Root-Device-Type = "ebs"
}
most_recent = true
owners = ["amazon"]
}
}
build {
sources = ["source.amazon-ebs.example"]
post-processors {
type = "aws-region-copy"
config {
source_region = "us-east-1"
destination_regions = ["us-west-2"]
ami_name_filter = "packer-example-*"
copy_tags = true
}
}
}
When you run packer init . in the same directory, Packer will automatically download the amazon and aws-region-copy plugins based on the required_plugins block. This is the first key concept: explicit plugin declaration. You tell Packer exactly which plugins you need, their versions, and where to get them from. This ensures reproducible builds, as you’re not relying on whatever version of a plugin might be lurking in a global cache.
Then, packer build . executes. The amazon-ebs source builds your AMI in us-east-1. Once that’s successfully completed, the aws-region-copy post-processor kicks in. It takes the AMI ID generated by the builder, uses the AWS SDK to initiate a copy operation to us-west-2, and waits for that copy to finish. The ami_name_filter ensures it’s copying the correct AMI, and copy_tags propagates any tags.
The mental model here is that Packer’s execution flow is a pipeline. Builders generate artifacts, and post-processors modify or act upon those artifacts. Plugins provide the implementations for both builders and post-processors, allowing you to extend Packer’s capabilities beyond its built-in functionality.
The source attribute in required_plugins is crucial. It points to a plugin registry, most commonly hashicorp.com/packer or a custom registry. For community plugins like aws-region-copy, it’s often github.com/hashicorp/. Packer uses this to locate and download the plugin binaries.
The version constraint ensures you’re using a compatible version. This prevents unexpected behavior caused by API changes or bugs in newer or older plugin versions.
The actual configuration for the plugin goes within the config block of the respective builder or post-processor. This is where you provide the specific parameters needed for that plugin to do its job.
The most surprising thing about plugin management is how deeply integrated they are with the packer init command. It’s not just a download step; it’s a full dependency resolution and installation process. Packer maintains a local plugin cache (typically in ~/.config/packer/plugins/), and packer init populates this cache based on your packer block. If you run packer build without packer init first, Packer will still attempt to download and cache the plugins, but init makes this explicit and allows you to review dependencies beforehand.
This plugin system is what gives Packer its power and flexibility, allowing it to interact with virtually any cloud provider, virtualization platform, or configuration management tool through custom or community-provided extensions.
The next thing you’ll likely encounter is managing plugins for custom or internal tools, which involves setting up your own plugin registry.