Choosing Between Packer Shell and Ansible Provisioners: A Practical Decision Guide
Shell vs Ansible provisioners in Packer: compare idempotency, performance, and complexity, then see real HCL2 templates and validation steps to decide which fits your image‑building workflow.
31 Mar 2026, 06:17 UTC

When to Pick a Provisioner in Packer
Building a machine image with HashiCorp Packer often requires one or more provisioning steps. The two most common provisioners are Shell (Bash, PowerShell, etc.) and Ansible. The decision hinges on several constraints:
- Complexity of configuration – a single package versus a multi‑service stack.
- Idempotency – need to guarantee repeatable runs without manual checks.
- Existing tooling – do you already maintain Ansible playbooks?
- Build performance – how much overhead can the build tolerate?
- Environment constraints – network access, SSH keys, and required binaries.
Feature Comparison
| Feature | Shell Provisioner | Ansible Provisioner |
|---|---|---|
| Idempotency support | Manual – script must guard against repeat runs | Built‑in – playbook tasks are idempotent by design |
| Complexity handling | Imperative scripting; easy for single tasks | Declarative; handles dependencies, roles, and orchestration |
| External dependencies | None beyond the interpreter in the base image | Requires Ansible on build host or SSH access to a controller |
| Provisioning time | Fast (seconds) | Higher overhead (seconds to minutes) due to inventory parsing |
| Reusability | Low – scripts are ad‑hoc | High – playbooks and roles can be reused across projects |
| Network requirements | None | Needs connectivity to repositories and any role sources |
| Learning curve | Minimal – shell syntax | Requires knowledge of Ansible YAML, modules, and inventory |
| Debug visibility | Raw script output is easy to read | Ansible shows task names and statuses; can be verbose |
| Rollback effect | None – scripts change state permanently | Can be rolled back if playbooks include handlers or clean‑up tasks |
Trade‑off Summary
- Shell wins when you need a quick, one‑off change and you want to avoid installing Ansible on the build host.
- Ansible wins when you already maintain infrastructure as code, need complex orchestration, or want to guarantee repeatable builds without writing idempotent scripts.
- Performance – Shell provisioning is usually
~5–10 s; Ansible can take30–120 sdepending on playbook size. - Maintenance – Shell scripts grow fragile quickly; Ansible playbooks scale better with reusable roles.
- Network – If the build image cannot reach external repositories, Shell is safer; Ansible must have access or you must bundle dependencies.
Concrete Implementation Example
1️⃣ Shell Provisioner Template (HCL2)
packer {
required_plugins {
amazon = {
version = ">= 1.0.0"
source = "github.com/hashicorp/amazon"
}
}
}
variable "aws_region" {
type = string
default = "us-east-1"
}
source "amazon-ebs" "ubuntu" {
ami_name = "packer-shell-demo-{{timestamp}}"
instance_type = "t2.micro"
region = var.aws_region
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
}
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl enable nginx",
"echo 'Hello, world!' | sudo tee /var/www/html/index.html"
]
}
}
Run the build:
packer validate shell-demo.pkr.hcl
packer build -debug shell-demo.pkr.hcl
Check the resulting AMI: ensure nginx is installed and the web page is served.
2️⃣ Ansible Provisioner Template (HCL2)
packer {
required_plugins {
amazon = {
version = ">= 1.0.0"
source = "github.com/hashicorp/amazon"
}
}
}
variable "aws_region" {
type = string
default = "us-east-1"
}
source "amazon-ebs" "ubuntu" {
ami_name = "packer-ansible-demo-{{timestamp}}"
instance_type = "t2.micro"
region = var.aws_region
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
ssh_username = "ubuntu"
}
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "ansible" {
playbook_file = "./setup-web.yml"
ansible_env = { ANSIBLE_STDOUT_CALLBACK = "default" }
use_become = true
become_user = "root"
}
}
The accompanying Ansible playbook (setup-web.yml):
- hosts: all
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Ensure nginx is running
service:
name: nginx
state: started
enabled: true
- name: Deploy index page
copy:
dest: /var/www/html/index.html
content: "Hello, Ansible!"
Validate and build:
packer validate ansible-demo.pkr.hcl
packer build -debug ansible-demo.pkr.hcl
After the build, verify that the AMI contains nginx and serves the index page. The Ansible logs will show each task’s status and whether it made changes.
Validation Checklist
- Run
packer validateto catch syntax errors before the build. - Use
-debugto capture provisioner output; verify that each step completes successfully. - Inspect the final artifact (AMI, Vagrant box, etc.) to confirm the desired state.
- For Ansible, ensure the build host has access to any required roles or repositories; otherwise the playbook will fail silently.
- Consider adding
ansible-playbook --checkas a pre‑build step if you want to simulate changes without applying them.
Practical Decision Flow
- Do you already own Ansible playbooks for the target stack?
• Yes → Prefer Ansible provisioner. - Is the build environment isolated with no outbound network access?
• Yes → Prefer Shell provisioner or bundle Ansible locally. - Do you need strict idempotency and rollback capabilities?
• Yes → Prefer Ansible provisioner. - Is the provisioning workload trivial (single package or config tweak)?
• Yes → Shell provisioner is simpler and faster. - Otherwise, default to Ansible for maintainability and scalability.
Limitations and Caveats
- Shell scripts can become error‑prone if idempotency logic is omitted; always guard against re‑execution.
- Ansible provisioning requires a functional SSH connection and may fail if the build machine cannot reach external repositories.
- Both provisioners run as the build user; if privileged operations are needed, ensure
sudoorbecomeis configured. - Provisioner logs can be verbose; consider redirecting output to a file for easier post‑build analysis.
Conclusion
Use the Shell provisioner for quick, isolated changes and when you want to avoid external dependencies. Opt for the Ansible provisioner when you already manage infrastructure with Ansible, need idempotent multi‑step configurations, or require reusable roles across projects. The decision table above and the concrete examples should help you pick the right tool for your Packer builds.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.