← Back to all projects

LEARN PACKER DEEP DIVE

Learn Packer: From Building Images to Building Packer

Goal: Deeply understand HashiCorp Packer—not just how to use it, but how it works fundamentally. Progress from building standard machine images to replicating Packer’s core logic and even extending it by writing your own plugin.


Why Go Deep on Packer?

Packer is the industry standard for creating “golden” machine images, a cornerstone of immutable infrastructure. Most developers learn how to write a Packer template and stop there. By going deeper, you will understand the entire lifecycle of an image build, how Packer communicates with cloud providers, and how its powerful plugin architecture works. This knowledge separates a user from an expert, enabling you to debug complex builds, automate any image creation process, and even contribute to the ecosystem.

After completing these projects, you will:

  • Master the Packer HCL syntax and its core concepts.
  • Build images for multiple platforms, including cloud AMIs, Docker containers, and Vagrant boxes.
  • Understand the internal mechanics of builders, communicators, and provisioners.
  • Be able to debug any failed Packer build by understanding its temporary state.
  • Have the skills to write your own Packer plugin from scratch.

Core Concept Analysis

How Packer Actually Works: The Internal State Machine

Packer is not magic. It’s a sophisticated state machine that executes a series of steps by making API calls and running remote commands.

┌─────────────────────────────────────────────────────────────────────────┐
│                          1. PARSE & VALIDATE                            │
│           Reads HCL template, validates syntax, builds dependency graph.          │
└─────────────────────────────────────────────────────────────────────────┘
                                     │
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                            2. BUILDER RUNS                              │
│  (e.g., amazon-ebs builder)                                             │
│    a. Create temporary resources (keypair, security group).             │
│    b. Launch source VM using cloud API.                                 │
└─────────────────────────────────────────────────────────────────────────┘
                                     │
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                          3. COMMUNICATOR CONNECTS                         │
│           Waits for IP, then connects (e.g., via SSH).                  │
└─────────────────────────────────────────────────────────────────────────┘
                                     │
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                           4. PROVISIONERS RUN                             │
│    a. File Provisioner -> `scp` files to the VM.                        │
│    b. Shell Provisioner -> `ssh user@host 'bash /tmp/script.sh'`        │
└─────────────────────────────────────────────────────────────────────────┘
                                     │
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                           5. IMAGE CREATION                             │
│           Calls cloud API to create a snapshot/image from the VM.       │
└─────────────────────────────────────────────────────────────────────────┘
                                     │
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                             6. TEARDOWN                                 │
│           Terminates VM, deletes temporary keypair/security group.      │
└─────────────────────────────────────────────────────────────────────────┘
                                     │
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                          7. POST-PROCESSORS RUN                           │
│           Takes artifact (e.g., AMI ID) and performs actions            │
│           (e.g., copy AMI to another region, write ID to file).         │
└─────────────────────────────────────────────────────────────────────────┘

Project List

These projects are designed to take you from a basic user to a potential contributor.


Project 1: Your First “Golden Image”

  • File: LEARN_PACKER_DEEP_DIVE.md
  • Main Programming Language: HCL
  • Alternative Programming Languages: Shell
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Immutable Infrastructure / Cloud Images
  • Software or Tool: Packer, AWS/GCP/Azure
  • Main Book: “Terraform: Up & Running” (for IaC concepts), Packer Official Documentation

What you’ll build: A minimal Packer template that launches an Ubuntu server on AWS, installs Nginx, and saves the result as a new Amazon Machine Image (AMI).

Why it teaches the basics: This is the fundamental Packer workflow. You’ll learn the core components (source, build, provisioner) and experience the process of turning a generic OS into a specialized appliance.

Core challenges you’ll face:

  • Setting up cloud credentials for Packer → maps to understanding how Packer authenticates with APIs
  • Writing your first source and build blocks → maps to defining the base image and the final artifact
  • Using a shell provisioner → maps to running commands on the remote machine to customize it
  • Finding your AMI in the cloud console → maps to verifying the real-world outcome of your build

Key Concepts:

  • HCL2 Syntax for Packer: Packer Docs - HCL2 Templates
  • Builders (source block): Packer Docs - amazon-ebs builder
  • Provisioners: Packer Docs - shell provisioner

Difficulty: Beginner Time estimate: A few hours Prerequisites: An AWS account (or other cloud), basic command-line skills.

Real world outcome: After running packer build ., the process will complete and output a new AMI ID. You can then go into your AWS EC2 console, find this AMI, and launch an instance from it. When you access that instance’s public IP, you will see the default Nginx welcome page.

Implementation Hints:

  • Use a source "amazon-ebs" "ubuntu" block to define your base. Give it an instance_type and source_ami_filter.
  • The source_ami_filter is important; use it to find the latest Ubuntu 22.04 LTS AMI automatically.
  • Your build block will be simple, just referring to the source.
  • The provisioner "shell" block can use inline commands like sudo apt-get update, sudo apt-get install -y nginx.

Learning milestones:

  1. packer init and packer validate pass → Your syntax is correct.
  2. Packer successfully launches a temporary instance → Your credentials and source block are correct.
  3. The build completes and outputs an AMI ID → Your provisioner worked, and the full lifecycle completed.
  4. An instance launched from your AMI serves the Nginx page → You’ve confirmed your immutable image is functional.

Project 2: Building a Docker Container

  • File: LEARN_PACKER_DEEP_DIVE.md
  • Main Programming Language: HCL
  • Alternative Programming Languages: Dockerfile
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Containerization / Image Building
  • Software or Tool: Packer, Docker
  • Main Book: “The Docker Book” by James Turnbull

What you’ll build: A Packer template that uses the docker builder to construct a custom Docker image from a base image, install a Python Flask application, and tag it for a registry.

Why it teaches Packer’s versatility: This project shows that Packer isn’t just for cloud VMs. Its plugin-based architecture allows it to target completely different platforms, like Docker. You’ll learn that the core concepts (source, provisioner) are the same, even when the builder’s mechanics are totally different.

Core challenges you’ll face:

  • Using the docker builder → maps to understanding how Packer interacts with the Docker daemon
  • Comparing Packer provisioning to a Dockerfile → maps to seeing the different philosophies (Packer is imperative, Dockerfile is declarative layers)
  • Using a post-processor to push the image → maps to automating the final step of shipping your artifact to a registry

Key Concepts:

  • Docker Builder: Packer Docs - docker builder
  • Post-Processors: Packer Docs - docker-push post-processor
  • Packer vs Dockerfile: Blog post by Nicholas Dille - “Packer vs. Dockerfile”

Difficulty: Beginner Time estimate: Weekend Prerequisites: Docker installed and running, basic Python knowledge.

Real world outcome: After running packer build ., you will have a new image in your local Docker registry (e.g., my-flask-app:1.0). You can then docker run -p 5000:5000 my-flask-app:1.0 and access http://localhost:5000 to see your running Flask application. If you configure the post-processor, the image will also be pushed to Docker Hub.

Implementation Hints:

  • Your source block will be docker, specifying a base image like python:3.9-slim.
  • Use a file provisioner to copy your app.py and requirements.txt into the container’s temporary filesystem.
  • Use a shell provisioner to run pip install -r requirements.txt.
  • Your build block’s changes attribute in the Docker builder is where you define things that become part of the final image’s metadata, like ENTRYPOINT and EXPOSE.

Learning milestones:

  1. Packer builds a Docker image successfully → You understand the Docker builder.
  2. Your provisioned app runs correctly inside the container → You’ve mastered provisioning in a container context.
  3. The image is tagged and pushed to a registry → You’ve used a post-processor to complete a CI/CD-like workflow.
  4. You can articulate when to use Packer vs. a Dockerfile for building containers → You understand the trade-offs.

Project 3: Debugging a Failed Build

  • File: LEARN_PACKER_DEEP_DIVE.md
  • Main Programming Language: HCL
  • Alternative Programming Languages: Shell
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Debugging / Systems Internals
  • Software or Tool: Packer
  • Main Book: Packer Official Documentation - Debugging

What you’ll build: An intentionally broken Packer build. Your goal is not to create an image, but to use Packer’s debug features to pause the build, SSH into the temporary instance, diagnose the problem live, and fix it.

Why it teaches the internals: This is the most crucial project for understanding what Packer does “behind the scenes”. By stopping the process mid-flight, you get to see the temporary resources (VMs, keys, security groups) and the state of the machine during provisioning. This demystifies the entire process.

Core challenges you’ll face:

  • Running Packer in debug mode → maps to using the -debug flag and understanding the interactive prompts
  • Connecting to the temporary instance → maps to using the temporary SSH key that Packer generates to log into the build VM
  • Diagnosing a failing provisioner → maps to manually running the script on the remote machine and figuring out why it’s failing
  • Cleaning up a failed build → maps to understanding the -on-error=cleanup flag and why you sometimes don’t want it

Key Concepts:

  • Debugging Packer: Packer Docs - Debugging
  • Error Handling: Packer Docs - Handling Errors
  • Communicators: Packer Docs - SSH Communicator

Difficulty: Intermediate Time estimate: A few hours Prerequisites: Project 1.

Real world outcome: You will run packer build -debug . on a template with a broken script. The build will pause after the machine is up. You will get an SSH command to connect, log in, find the broken script in /tmp/, fix it, and resume the build, which will now complete successfully.

Implementation Hints:

  • In your shell provisioner, add a command that is guaranteed to fail, like exit 1 or trying to access a file that doesn’t exist.
  • Run packer build -debug .. When Packer pauses and asks you what to do, press Enter to pause at the step before the provisioner runs.
  • Packer will print the command to SSH into the machine. Copy and run this from another terminal.
  • Once inside, ls -la /tmp to find your provisioner script. Run it manually to see the error.
  • You can either fix the script locally and restart the build, or fix it on the remote machine and then tell Packer to retry the step.

Learning milestones:

  1. You successfully pause a build and SSH into the temporary instance → You’ve pierced the veil of abstraction.
  2. You can locate and manually execute the provisioner script → You understand how Packer runs commands.
  3. You can live-debug and fix the script → You’ve gained a critical skill for fixing complex builds.
  4. You understand what temporary resources are left behind when a build fails → You understand the importance of cleanup.

Project 4: Replicate a Builder, Manually

  • File: LEARN_PACKER_DEEP_DIVE.md
  • Main Programming Language: Shell (Bash)
  • Alternative Programming Languages: Python (with Boto3), Go
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Cloud APIs / Automation
  • Software or Tool: AWS CLI (or equivalent), ssh, scp
  • Main Book: Your cloud provider’s CLI documentation.

What you’ll build: A single shell script that does exactly what packer build does for the amazon-ebs builder. Your script will use the AWS CLI to launch an instance, wait for it, SSH into it, run a command, create an AMI, and clean up.

Why it teaches the internals: This project is the “behind the scenes”. By writing your own builder from scratch, you will gain a first-principles understanding of every API call and remote command Packer orchestrates. You will never see Packer as a black box again.

Core challenges you’ll face:

  • Scripting the AWS CLI → maps to programmatically creating key pairs, security groups, and instances
  • Implementing a “wait” loop → maps to polling the instance state until it’s “running” and SSH is available
  • Handling remote execution → maps to using ssh and scp to run your “provisioning” step
  • Orchestrating the cleanup → maps to ensuring the temporary instance and keys are deleted even if a step fails

Key Concepts:

  • Cloud Instance Lifecycle: AWS Docs - EC2 Instance Lifecycle
  • Remote Command Execution: ssh(1) man page
  • API Scripting: AWS CLI Command Reference

Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 3, strong shell scripting skills.

Real world outcome: You will run a single ./manual_packer.sh. The script will print out its progress as it creates resources, connects, provisions, and finally, it will output a new AMI ID and state that it has cleaned up the temporary resources—exactly like the real Packer.

Implementation Hints:

  • Use aws ec2 create-key-pair and save the key material to a local file. chmod 400 on it.
  • Use aws ec2 create-security-group and authorize-security-group-ingress to allow port 22.
  • Use aws ec2 run-instances to launch the VM. You’ll need to parse the JSON output with jq to get the InstanceId.
  • Write a while loop that calls aws ec2 describe-instances until the status is running. Then, use nc -zv <ip> 22 in a loop to wait for the SSH port.
  • Use ssh -i key.pem user@ip "sudo apt-get update" to provision.
  • Finally, call aws ec2 create-image and then aws ec2 terminate-instances.

Learning milestones:

  1. Your script launches an instance and can SSH into it → You’ve mastered the setup and communication steps.
  2. Your script can run a remote command and create an image → You’ve replicated the core provisioning and imaging steps.
  3. Your script reliably cleans up all temporary resources → You’ve implemented a robust state machine.
  4. You can map every line of your script to a corresponding line in a PACKER_LOG=1 output → You have a perfect mental model of how Packer works.

Project 5: Build a Vagrant Box from an ISO

  • File: LEARN_PACKER_DEEP_DIVE.md
  • Main Programming Language: HCL
  • Alternative Programming Languages: Shell
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: OS Installation / Local Virtualization
  • Software or Tool: Packer, VirtualBox, Vagrant
  • Main Book: “The Packer Book” by James Turnbull

What you’ll build: A Packer template that uses the virtualbox-iso builder to perform a fully unattended installation of a Linux OS (like Ubuntu Server) from an ISO file, configure it with a Vagrant user, and package it as a .box file for use with Vagrant.

Why it teaches low-level building: Unlike cloud builders that start from a pre-made image, you’re starting from a blank disk and an OS installer. This forces you to understand boot commands, automated OS installation (kickstart/preseed), and how to configure a base OS for virtualization.

Core challenges you’ll face:

  • Automating the OS installer → maps to using boot_command to send keyboard inputs to the installer to select language, keyboard, etc.
  • Serving a preseed/kickstart file → maps to using Packer’s built-in HTTP server to provide an unattended installation configuration file to the VM
  • Configuring the machine for Vagrant → maps to creating the vagrant user, setting up passwordless sudo, and installing the VirtualBox guest additions
  • Using the vagrant post-processor → maps to correctly packaging the resulting VM into a .box file

Key Concepts:

  • virtualbox-iso Builder: Packer Docs - virtualbox-iso
  • Boot Commands: Understanding how to interact with a BIOS/bootloader.
  • Unattended OS Installation: Ubuntu Docs - Preseeding
  • Vagrant Box Format: Vagrant Docs - Boxes

Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 1, familiarity with Linux installation.

Real world outcome: Running packer build . will download an Ubuntu ISO, start a new VirtualBox VM, automatically complete the entire OS installation without any user input, shut down, and output a ubuntu.box file. You can then run vagrant box add my-ubuntu ubuntu.box and vagrant init my-ubuntu; vagrant up to use your custom-built box.

Implementation Hints:

  • The boot_command is an array of strings that simulate typing at the boot prompt. You’ll need to enter things like <enter>, <wait>, etc., to navigate the installer menus.
  • For an Ubuntu server, you’ll provide a preseed file via the http_directory and a boot_command that points the installer to http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg.
  • Your preseed file must contain all the answers to the installer’s questions.
  • You’ll need a final shell provisioner script that creates the vagrant user, sets up the insecure public key for SSH, and installs the VirtualBox guest additions for shared folders to work.

Learning milestones:

  1. The OS installer boots and starts automatically → Your boot_command is correct.
  2. The OS installation completes without any prompts → Your preseed/kickstart file is comprehensive.
  3. The final VM can be SSH’d into by Packer → Your user and SSH setup is correct.
  4. You can add and use the final .box file with Vagrant → You’ve successfully built a complete, from-scratch Vagrant box.

Project 6: Write a Custom Post-Processor in Go

  • File: LEARN_PACKER_DEEP_DIVE.md
  • Main Programming Language: Go
  • Alternative Programming Languages: N/A
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 4: Expert
  • Knowledge Area: Go Programming / Packer Plugins
  • Software or Tool: Packer, Go
  • Main Book: Packer SDK Documentation on GitHub.

What you’ll build: A simple post-processor plugin, written in Go. This plugin will take the AMI ID from an amazon-ebs build and write it into a Terraform .tfvars file, making it easy to consume the new AMI in a Terraform project.

Why it teaches the plugin system: This is your first step into extending Packer. You’ll learn how Packer communicates with plugins, how data (like the artifact ID) is passed, and how to compile and install a custom plugin. It demystifies the “magic” of how Packer can support so many platforms.

Core challenges you’ll face:

  • Setting up a Go project for a Packer plugin → maps to correctly importing the Packer plugin SDK
  • Implementing the PostProcessor interface → maps to writing the Configure and PostProcess methods
  • Handling artifacts and state → maps to accessing the completed artifact’s ID and writing it to a file
  • Compiling and installing the plugin → maps to placing your compiled binary where Packer can find it

Key Concepts:

  • Packer Plugin SDK: Packer Docs - Extending Packer
  • Go Interfaces: “The Go Programming Language” by Donovan & Kernighan, Ch. 7
  • Plugin Installation: Packer Docs - Installing Plugins

Difficulty: Expert Time estimate: 1-2 weeks Prerequisites: Project 1, basic Go programming knowledge.

Real world outcome: You will compile your Go code into a packer-plugin-tfvars binary. After installing it, you’ll add a post-processor "tfvars" { ... } block to your Packer template. When the build finishes, a new file named packer.auto.tfvars will be created in your directory, containing ami_id = "ami-xxxxxxxxxxxxxxxxx".

Implementation Hints:

  • Your Go project will need to import github.com/hashicorp/packer-plugin-sdk/packer and github.com/hashicorp/packer-plugin-sdk/plugin.
  • Create a PostProcessor struct.
  • The PostProcess method receives a ui for logging, and an artifact.
  • Check the artifact.BuilderId() to make sure you’re only running for amazon-ebs.
  • The artifact.Id() method will give you the AMI ID.
  • Your main function will use plugin.Serve to serve your PostProcessor implementation.
  • To install, go build your plugin and copy the binary to ~/.packer.d/plugins/.

Learning milestones:

  1. Your Go program compiles successfully → Your project setup and dependencies are correct.
  2. Packer recognizes and validates your post-processor → Your plugin is correctly installed and configured.
  3. The post-processor runs after a build → You’ve successfully hooked into the Packer lifecycle.
  4. The correct .tfvars file is created with the new AMI ID → Your plugin logic is working end-to-end.

Summary

Project Main Tool Main Language Difficulty
1. Your First “Golden Image” Packer HCL Beginner
2. Building a Docker Container Packer HCL Beginner
3. Debugging a Failed Build Packer HCL Intermediate
4. Replicate a Builder, Manually AWS CLI Shell Advanced
5. Build a Vagrant Box from an ISO Packer HCL Advanced
6. Write a Custom Post-Processor Packer/Go Go Expert