Packer’s manifest file captures the exact outputs of a build, acting as a definitive record of what was produced.
Let’s see it in action. Imagine we’re building an AWS AMI using Packer. Here’s a simplified packer.json template:
{
"builders": [
{
"type": "amazon-ebs",
"region": "us-east-1",
"source_ami_filter": {
"filters": {
"name": "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
},
"most_recent": true
},
"instance_type": "t2.micro",
"ssh_username": "ubuntu",
"ami_name": "my-ubuntu-ami-{{timestamp}}"
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"echo 'Hello from Packer!' > /tmp/hello.txt"
]
}
]
}
When you run packer build packer.json, Packer will provision an EC2 instance, run the shell command, and then create an AMI from that instance. By default, Packer writes a builds.json file in the same directory where you ran the packer build command. This file is the manifest.
Here’s a snippet of what that builds.json might look like after a successful build:
[
{
"artifact_id": "us-east-1:ami-0123456789abcdef0",
"build_name": "amazon-ebs.amazon-instance",
"files": null,
"locations": [
"us-east-1"
],
"type": "amazon-ebs"
}
]
This JSON structure is the core of the manifest. It’s an array of objects, where each object represents a successful build artifact. The artifact_id is the crucial piece of information for cloud builders, like the AMI ID in this AWS example. For other builders, it might be a container image ID, a Vagrant box name, or a different identifier. The build_name maps back to the name field within a builder configuration in your template (or defaults if not specified). locations shows where the artifact was created, which is particularly useful for multi-region deployments.
The manifest isn’t just for passive observation; it’s designed to be programmatically consumed. You can use the packer build -machine-readable flag, which outputs JSON lines to standard output, or specifically request the manifest file. The true power comes when you integrate Packer into CI/CD pipelines. After a successful build, your pipeline script can parse this builds.json to retrieve the exact artifact_id and use it for subsequent deployment steps. This ensures that you’re always deploying the exact image that was tested and verified, eliminating the "it worked on my machine" problem at the image level.
For example, if you wanted to use the AMI ID from the manifest in a subsequent script, you could do something like this (assuming builds.json is in the current directory):
AMI_ID=$(jq -r '.[0].artifact_id' builds.json | cut -d':' -f2)
echo "The newly built AMI ID is: $AMI_ID"
# Now you can use $AMI_ID to launch EC2 instances or update Auto Scaling Groups
This simple jq command extracts the artifact_id from the first (and in this case, only) artifact in the builds.json array, and then cut cleans it up to just the AMI ID.
The manifest also plays a role when you use Packer’s post-processors. These are actions that run after an artifact has been built. Many post-processors, like docker-tag, docker-push, or artifactory, rely on the information within the manifest to know what to tag, push, or upload. The post-processor receives the manifest as input and can then operate on the identified artifacts.
It’s important to remember that the manifest file is generated after all builders and provisioners have completed successfully. If a build fails at any point, the builds.json file will either be incomplete or not created at all, depending on when the failure occurred. This behavior is by design, ensuring that you only have records for successful, usable artifacts.
The artifact_id field is not always a simple string. For builders that create multiple artifacts (e.g., a builder creating AMIs in multiple regions), the locations field will contain multiple entries, and the artifact_id will often be prefixed with the region, like us-east-1:ami-0123456789abcdef0. You’ll need to parse this carefully if your pipeline needs to interact with specific regions.
Understanding the structure of builds.json is key to automating your image creation and deployment workflows. It provides a machine-readable contract between your build process and whatever needs to consume the resulting artifacts.
The next step after mastering manifests is understanding how to use Packer’s variables to parameterize your builds for greater flexibility.