Packer: Build Machine Images

Khimananda Oli 8 min read Virtualization
Packer: Build Machine Images

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.

HCL TemplateSource + ProvisionerValidate & PlanSyntax + Auth CheckTemp InstanceProvision & ConfigureSnapshot / AMICapture Final StateArtifactReady to DeployPacker orchestrates ephemeral resources; nothing persists except the final image artifact
Packer build machine images lifecycle: from HCL definition through ephemeral provisioning to final artifact capture

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.

Shell Provisionerapt-get update && upgradeInstall base packagesOS hardening scripts✓ Fast • ✗ Hard to test✗ Not idempotent by defaultAnsible ProvisionerRole: common-packagesRole: security-hardeningRole: app-dependencies✓ Idempotent • ✓ Testable✓ Reusable across projectsInspec ValidationCIS Benchmark ProfileCustom Compliance ControlsFail build on violation✓ Audit-ready evidence✓ Prevents bad images
Packer provisioner comparison: shell for speed, Ansible for maintainability, Inspec for compliance validation

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:

  1. Trigger on merge to main: Only build images from protected branches. Feature branches run packer validate only.
  2. 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.
  3. Tag with immutable metadata: Include git SHA, pipeline run ID, and SBOM hash in image tags. Never reuse tags.
  4. Run vulnerability scanning: Integrate Trivy or Grype as a post-processor. Fail the build if critical CVEs are found.
  5. 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.

CriteriaHashiCorp PackerAWS EC2 Image BuilderAzure Image Builder
Multi-cloud supportNative (AWS, Azure, GCP, VMware, etc.)AWS onlyAzure only
Configuration languageHCL2 (portable, versionable)YAML/JSON (AWS-specific schema)ARM/Bicep + JSON (Azure-specific)
Provisioner ecosystemShell, Ansible, Chef, Puppet, InspecShell, Ansible (limited)Shell, PowerShell, Ansible
CI/CD integrationCLI-first, any pipeline systemCodePipeline native, others via APIAzure DevOps native, others via REST
Compliance evidenceManifest + custom post-processorsBuilt-in STIG/CIS componentsPolicy definitions integration
Learning curveModerate (HCL + cloud APIs)Low for AWS usersModerate (ARM complexity)
Best forMulti-cloud, hybrid, compliance-heavyAWS-only shops needing STIGAzure-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: Need Golden Image?Multiple Cloud Providers?YesNoUse PackerPortable HCL, Multi-CloudSingle Cloud + Deep Integration?NoYesStill Use PackerSimpler than learning vendor lock-inCloud-NativeImage BuilderDefault to Packer unless you have specific vendor-integration requirements
Decision framework: when to choose Packer versus cloud-native image builders for golden image automation

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.

Frequently Asked Questions

Packer automates creating identical machine images for multiple platforms from a single source configuration, ensuring consistent infrastructure across cloud and on-premise environments.

Yes, the core CLI tool is open source and free. HCP Packer offers paid features like image metadata tracking and registry integration for enterprise teams.

Packer builds immutable machine images with pre-installed software. Terraform provisions and manages infrastructure using those images but does not create the underlying OS artifacts itself.

Common provisioners include shell, Ansible, Chef, Puppet, PowerShell, and file upload. Shell scripts remain the most portable option for installing packages and configuring services during builds.

Yes, define multiple builders in one template to generate AMIs and Managed Images concurrently. Each builder runs independently, allowing parallel image creation across different cloud providers.

Run packer validate followed by your template filename. This checks syntax, variable definitions, and builder configuration without executing any API calls or provisioning steps.

Increase ssh_timeout to 10m or higher for slow-booting instances. Verify security groups allow port 22 access and confirm the correct username matches the base image default.

Yes, specify instance_type as t4g.micro or equivalent ARM instances in AWS builders. Ensure your provisioner scripts and package repositories support aarch64 architecture natively.

Use smaller base instances, enable snapshot caching, minimize provisioner count, and parallelize independent installation steps. Pre-bake common dependencies into custom base images to avoid repeated setup.

Never hardcode credentials. Use environment variables, vault integration, or cloud-native secret managers. Reference them via sensitive variables in HCL to prevent logging exposure during builds.

Absolutely. Run packer init then packer build in GitHub Actions, GitLab CI, or Jenkins. Store templates in version control and trigger builds on merge to main branch.

Add -debug flag to pause between steps and inspect the running instance manually. Check /var/log/cloud-init-output.log and provisioner logs for specific error messages and exit codes.

HCL2 is now the standard configuration format since version 1.7. Migrate legacy JSON files using packer hcl2upgrade command to gain better variable handling and block structure.

Rebuild monthly or when critical security patches release. Automate scheduled builds via CI to maintain fresh images with updated packages, reducing drift and vulnerability exposure over time.

No, Packer targets virtual machines and cloud instances. Use Dockerfiles for container images. Packer focuses exclusively on VM-based infrastructure like EC2, GCE, and VMware.