
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Drift is the silent killer of production stability, and manual server configuration is its primary vector. Using Packer: Build Machine Images automates the creation of identical, pre-configured base images across AWS, Azure, and GCP, eliminating configuration drift before your application even deploys. This approach shifts security patching and dependency installation left into the build phase, ensuring every instance launches in a known-good state rather than relying on fragile boot-time scripts.
How does Packer: Build Machine Images work in practice?
Packer operates on a declarative model where you define the desired end state of an image rather than the imperative steps to reach it. The tool orchestrates a temporary build environment, applies your configuration, validates the result, and captures the final artifact. Understanding this lifecycle is critical because debugging a failed image build requires knowing exactly which phase broke.
The process begins with packer validate, which checks syntax and verifies cloud credentials without launching resources. During packer build, Packer launches a temporary VM or container using the specified source block. It then connects via SSH or WinRM to execute provisioners—shell scripts, Ansible playbooks, or file uploads—in the order defined. Once provisioning completes successfully, Packer triggers the platform's native snapshot mechanism (EBS snapshot for AWS, managed image for Azure) and tags the resulting artifact with metadata like git commit SHA or build timestamp. Finally, it terminates the temporary instance. If any step fails, Packer halts and optionally leaves the instance running for debugging when -debug is passed.
This ephemeral nature is what makes immutable infrastructure and golden images with Packer so reliable. You never modify a running production server; you replace it entirely with a new image built from a version-controlled template. For teams managing compliance frameworks like SOC 2 or ISO 27001, this provides an auditable trail: every image maps to a specific git commit, and every configuration change requires a new build.
How do you write a multi-cloud Packer HCL template?
Modern Packer uses HCL2 (.pkr.hcl files), which supports variables, locals, and multiple source blocks in a single template. This is essential for teams operating across regions or clouds. A common mistake I see in Nepal-based startups expanding globally is maintaining separate JSON templates per region; HCL eliminates this duplication entirely.
Define reusable variables and sources
Start by externalizing all environment-specific values. Never hardcode AMI IDs, subnet IDs, or credentials in your template.
variable "aws_region" {
type = string
default = "ap-south-1"
}
variable "base_ubuntu_ami" {
type = string
default = "ami-0c55b159cbfafe1f0"
}
variable "build_version" {
type = string
default = env("GIT_COMMIT")
}
source "amazon-ebs" "ubuntu" {
ami_name = "golden-ubuntu-${var.build_version}"
instance_type = "t3.micro"
region = var.aws_region
source_ami = var.base_ubuntu_ami
ssh_username = "ubuntu"
tags = {
BuildVersion = var.build_version
Builder = "packer"
Compliance = "soc2-ready"
}
} Add provisioners for configuration
Provisioners should be idempotent and fail-fast. Always include set -euo pipefail in shell scripts to catch errors early. For complex configurations, delegate to Ansible or Chef rather than embedding hundreds of lines of bash.
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
inline = [
"set -euo pipefail",
"sudo apt-get update -y",
"sudo apt-get upgrade -y",
"sudo apt-get install -y curl wget unzip",
"sudo systemctl enable unattended-upgrades"
]
}
provisioner "ansible" {
playbook_file = "./ansible/harden.yml"
extra_arguments = ["--extra-vars", "build_version=${var.build_version}"]
}
post-processor "manifest" {
output = "manifest.json"
strip_path = true
}
} This structure lets you add Azure or GCP sources later without touching provisioner logic. Simply define source "azure-arm" or source "googlecompute" blocks and include them in the build.sources array. Packer builds them sequentially by default, or in parallel with -parallel-builds=N.
What provisioners should you use for secure golden images?
Choosing the right provisioner determines whether your images are truly secure and maintainable. In my experience auditing infrastructure for Nepali fintech companies, I've found that teams often over-rely on shell scripts when configuration management tools provide better idempotency and testing.
- Shell provisioner: Best for simple package installs and OS-level tuning. Keep scripts under 50 lines; beyond that, extract to a dedicated config management tool.
- Ansible provisioner: Ideal for complex application stacks, user management, and security hardening. Supports roles, vault encryption, and dry-run testing outside Packer.
- File provisioner: Use sparingly for static configs or certificates. Prefer templating with
templatefile()in HCL for dynamic content. - Inspec/Serverspec: Add as a final validation provisioner to verify compliance before snapshotting. This catches misconfigurations that pass silently.
A critical security practice: never bake secrets into images. Use runtime injection via environment variables, cloud-init, or secrets managers like HashiCorp Vault. Your image should contain only public configuration; sensitive data belongs in the deployment layer. This aligns with Kubernetes secrets management done right principles, where secrets are decoupled from artifacts entirely.
How do you integrate Packer into CI/CD pipelines safely?
Running Packer manually works for learning but violates reproducibility principles in production. Every image must originate from a pipeline triggered by code changes, not human intervention. Here's the pattern I implement for clients requiring audit-ready infrastructure:
- Trigger on merge to main: Only build images from protected branches. Feature branches run
packer validateonly. - Inject credentials securely: Use OIDC federation (AWS IAM Roles Anywhere, Azure Workload Identity) instead of long-lived access keys. See deploy to AWS from GitHub Actions with OIDC for implementation details.
- Tag with immutable metadata: Include git SHA, pipeline run ID, and SBOM hash in image tags. Never reuse tags.
- Run vulnerability scanning: Integrate Trivy or Grype as a post-processor. Fail the build if critical CVEs are found.
- Publish manifest to artifact store: Store manifest.json alongside your Terraform state so deployments reference exact image IDs.
A common pitfall in Nepali teams adopting this workflow is skipping the validation-only stage for feature branches. This leads to broken main builds and wasted cloud spend. Always validate cheaply before committing to expensive image builds. Also, set aggressive timeouts; a hung Packer build can leak cloud resources costing hundreds of dollars overnight.
Packer vs Cloud-Native Image Builders: Which should you choose?
While Packer remains the industry standard for multi-cloud image building, cloud providers now offer native alternatives. Choosing correctly depends on your operational constraints, not hype.
| Criteria | HashiCorp Packer | AWS EC2 Image Builder | Azure Image Builder |
|---|---|---|---|
| Multi-cloud support | Native (AWS, Azure, GCP, VMware, etc.) | AWS only | Azure only |
| Configuration language | HCL2 (portable, versionable) | YAML/JSON (AWS-specific schema) | ARM/Bicep + JSON (Azure-specific) |
| Provisioner ecosystem | Shell, Ansible, Chef, Puppet, Inspec | Shell, Ansible (limited) | Shell, PowerShell, Ansible |
| CI/CD integration | CLI-first, any pipeline system | CodePipeline native, others via API | Azure DevOps native, others via REST |
| Compliance evidence | Manifest + custom post-processors | Built-in STIG/CIS components | Policy definitions integration |
| Learning curve | Moderate (HCL + cloud APIs) | Low for AWS users | Moderate (ARM complexity) |
| Best for | Multi-cloud, hybrid, compliance-heavy | AWS-only shops needing STIG | Azure-centric enterprises |
In practice, I recommend Packer for any organization operating in more than one cloud or requiring portable compliance evidence. Cloud-native builders excel when you're fully committed to a single provider and need deep integration with their security baselines. For Nepali companies serving global clients across AWS and Azure simultaneously, Packer's portability justifies the additional abstraction layer.
Start Building Reproducible Machine Images Today
Packer: Build Machine Images transforms infrastructure from fragile snowflakes into reproducible, auditable artifacts. Start with a single-cloud HCL template, validate it in CI before automating builds, and gradually expand provisioners as your compliance needs grow. The upfront investment in templating pays dividends in reduced incident response time and faster audit cycles. If you need help designing a golden image strategy that meets both operational and compliance requirements, reach out to discuss your infrastructure.