Decouple Build and Post‑Build Logic in Packer with the shell‑local Post‑Processor
Using Packer’s shell‑local post‑processor lets you run host‑only scripts after an AMI build, keeping the image clean and adding side‑effects like Docker image creation or artifact upload. This article walks through a concrete example, trade‑offs, and actionable steps.
18 Oct 2025, 18:56 UTC

Problem
When building an Amazon Machine Image (AMI) with Packer, the template often grows as you add more steps: installing packages, running tests, uploading artifacts, and cleaning up. Mixing all of this into the builder section makes the template hard to read and reuse. You also risk leaking host‑only resources (like a local Docker daemon) into the guest image, which defeats the purpose of keeping the image minimal.
Thesis
The shell‑local post‑processor lets you separate the pure image‑creation logic from any local side‑effects. After the builder finishes, Packer runs a script on the machine that invoked the build, not inside the VM. This keeps the image clean, promotes idempotent builds, and gives you a single place to handle tasks that should only run once per build.
How to Use shell‑local
Adding a shell‑local post‑processor is a matter of inserting a post-processors block in your template:
{
"builders": [
{
"type": "amazon-ebs",
"ami_name": "my-app-{{timestamp}}",
"source_ami": "ami-0abcdef1234567890",
"instance_type": "t3.micro",
"ssh_username": "ec2-user",
"region": "us-west-2"
}
],
"post-processors": [
{
"type": "shell-local",
"scripts": ["install-on-host.sh", "cleanup.sh"],
"environment": [
"DOCKER_HOST=tcp://127.0.0.1:2375",
"REGISTRY_USER=${REGISTRY_USER}",
"REGISTRY_PASS=${REGISTRY_PASS}"
]
}
]
}
Key points:
scriptsis a list of shell files that Packer will execute in order.- Environment variables can be injected so sensitive data stays outside the template.
- Because the script runs on the builder host, it can talk to services like Docker, Git, or local file systems.
Worked Example: Installing a Package on the Host and Uploading a Docker Image
Suppose you build an AMI that contains your application, but you also want to:
- Install
dockeron the build host. - Build a lightweight container image from the AMI’s artifacts.
- Push that image to a private registry.
- Clean up temporary files.
Here’s how you can achieve this with shell‑local.
install-on-host.sh
#!/usr/bin/env bash
set -euo pipefail
# Only run if docker is missing
if ! command -v docker > /dev/null; then
echo "Installing Docker…"
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
rm get-docker.sh
else
echo "Docker already present"
fi
# Pull the latest base image used for the container
docker pull ${BASE_IMAGE:="public.ecr.aws/amazonlinux/amazonlinux:2"}
cleanup.sh
#!/usr/bin/env bash
set -euo pipefail
# Remove any temporary files created by the previous script
rm -f get-docker.sh
post‑processor logic (inside install-on-host.sh)
# Build a container image from files extracted from the AMI
# Assume the AMI build produced a directory /tmp/ami-artifacts
mkdir -p /tmp/container
cp -r /tmp/ami-artifacts/* /tmp/container/
cat > /tmp/container/Dockerfile <
After packer build finishes, the output logs will show the steps executed by install-on-host.sh. The Docker image is pushed to your registry, while the AMI remains untouched by Docker installation.
Trade‑offs and Limitations
- Host‑only execution: Scripts run on the machine invoking Packer. If you need to access the state of the VM (e.g., check a file created inside the image), a
shellpost‑processor that runs inside the guest must be used instead. - Security: Any secrets injected via environment variables are visible in the Packer logs unless you use Packer’s
secretfeature. Avoid hard‑coding credentials in the script files. - Idempotence: The post‑processor can perform actions that change between runs (like pulling the latest Docker image). If you require deterministic builds, keep such logic in a separate CI step rather than the post‑processor.
- Resource consumption: Running heavy tasks (e.g., building large Docker images) inside a post‑processor can delay the overall build pipeline. Consider offloading to an external build system if the operation is time‑consuming.
Actionable Next Steps
- Define a dedicated
shell‑localblock in your Packer template to handle host‑only tasks. - Keep the builder section focused on creating a clean, minimal image.
- Use environment variables or Packer secrets to pass sensitive data to your scripts.
- Add verification steps: after the build, SSH into the instance to confirm the image contains the expected packages, and run
docker imagesto verify the container was pushed. - Document the post‑processor workflow in your CI pipeline so future maintainers understand the separation of concerns.
By decoupling build and post‑build logic, you maintain a lean template, reduce build complexity, and gain flexibility to perform host‑specific operations without polluting the guest image.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.