Packer is an open-source VM image creation tool from Hashicorp. It helps you automate the process of Virtual machine image creation on the cloud and on-prem virtualized environments.
To put it simply, whatever manual steps you perform to create a Virtual machine image can be automated through a simple Packer config template. You declare the state of the VM image you need, and Packer builds it for you.
Packer Use Cases
The following are the main use cases for Packer.
- Golden Image Creation: With packer, you can template the configurations required for a golden VM image that can be used across organizations.
- Monthly VM Patching: You can integrate Packer into your monthly VM image patching pipeline.
- Immutable Infrastructure: If you want to create an immutable infrastructure using VM images as a deployable artifact, you can use Packer in your CI/CD pipeline.
Basically, all the above use cases are part of the CI pipelines or IaaC code pipelines where every Packer template is version controlled. Even the packer template development or update should go through all the CI tests before getting deployed in a project environment.
Packer Tutorial For Beginners
In this Packer beginner tutorial, you will learn the following.
- The basic building blocks of Packer.
- Packer Workflow
- Installing Packer
- Understanding Packer HCL template.
- Creating an AWS AMI using Packer
Let’s get started with the Packer setup.
Install Packer
You can have Packer installed on your local workstation or on a cloud instance. In the actual project, the Packer installation would be part of the Jenkins agent or any tooling which is part of the CI/CD process.
Note: If you are setting up Packer on your workstation for debugging or testing purposes, you need the AWS access and secret keys for Packer to interact with AWS services. Make sure you have the AWS access keys set on the file
~/.aws/credentials
. It is not mandatory to have a credentials file. You can pass the AWS credentials using the packer variable declaration as well.
Step 1: Download the latest Packer executable from the Packer downloads page. https://www.packer.io/downloads.html. It is available for Windows, Linux, and other Unix platforms.
For Linux, you can install Packer from the respective package managers or you can get the download link from the download button and download it using wget
.
For example, In Ubuntu
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install packer
Step 2: Verify packer installation by executing the packer command.
packer version
You should see the output as shown below.
Building VM Image (AWS AMI) Using Packer
Building a VM image using Packer is a simple process. The workflow is somewhat similar to building VMs using Vagrant
Take a look at the following high-level Packer workflow diagram.
Let’s break down the process.
- Declare all the required VM configurations in an HCL (Hashicorp configuration language) or a JSON file. Let’s call it the Packer template.
- To build the VM image, execute Packer with the Packer template..
- Packer authenticates the remote cloud provider and launches a server. If you execute Packer from a cloud environment, it leverages the cloud service account for authentication.
- Packer takes a remote connection to the server (SSH or Winrm).
- Then it configures the server based on the provisioner you specified in the Packer template (Shell script, Ansible, Chef, etc).
- Registers the AMI
- Deletes the running instance.
Packer Template Reference
All the code used in this article is hosted on a Packer Template Github repository. You can clone or fork it to keep it as a reference. Or you can use the template directly from the packer-aws-shell
folder
git clone https://github.com/techiescamp/packer-templates
Please feel free to contribute to the repo with more templates.
Packer HCL Template Explained
Note: From version
v1.7
Packer uses HCL format templates. JSON support is also there. But it’s recommended to change to HCL format as the newly developed features will not be added to the JSON-based template. If your exiting project or organization users JSON templates, you can convert them to HCL templates. See Upgrading JSON to HCL for more information.
Let’s have a look at the HCL Packer template in detail.
Here is the high-level packer template structure.
An HCL template has a few blocks. Let’s understand each block with sample configuration before we create the AMI with Packer
Variables
Using the variable block, you can declare the default Packer parameters as variables. The declared variables can be overridden from the command line during the packer build process.
You can make use of packer variables for dynamic configuration when used with CI tools like Jenkins.
Let’s understand different scenarios for variable usage.
Using Variable Within Template
The variables
block holds all the default variables within a template. An example is shown below.
variable "ami_id" {
type = string
default = "ami-6f68cf0f"
}
The declared variables can be accessed in other parts of the template using "${var.app_name}"
syntax. An example is shown below.
source_ami = "${var.ami_id}"
Using Environment Variables in Templates
Packer lets you use the system environment variables. First, you need to declare the environment variables in the variable section to use them in the other parts of the template.
Let’s say, you want to use the SCRIPT_PATH
environment variable that holds the path to a shell script that has to be used in the shell provisioner. You can declare that variable as shown below.
variable "script_path" { default = env("SCRIPT_PATH") }
Then, you can use the script_path
variable in the provisioned block like shown below.
provisioner "shell" { script = "${var.script_path}/demo-script.sh" }
Using Command Line Variables
All the variables declared in the variable block can be overridden during the Packer run time using -var
a flag followed by the variable value.
For example,
packer build -var 'app_name=httpd' ami.pkr.hcl
Note: In the above example,
ami.pkr.hcl
is the packer template file.
Using an HCL file
You can also use an HCL file to eternalize all the variables in the template. Let’s say we have a variable file named vars.packer.hcl
, you can add the variables as shown below.
app_name = "httpd"
ami_id = "ami-6f68cf0f"
And during the packer build, you can pass this file as shown below.
packer build -var-file="vars.packer.hcl"
Using a JSON File
You can also list all the variables in a JSON file and pass it during the packer build as shown below.
packer build -var-file=variables.json ami.pkr.hcl
locals
Locals are also variables, but they cannot be overridden. It is scoped within the Packer template.
locals {
app_name = "httpd"
}
You can interpolate a variable with a local as shown below.
locals {
ami_name = "${var.app_name}-prodcution"
}
If you want to pass any sensitive information in a variable, you can convert it to a local value and then mark it as sensitive. This way, the value won’t be printed anywhere in the Packer build logs.
local "secret_key" {
key = "${var.secret_key}"
sensitive = true
}
Source
Packer works on the concept of the builder plugin. For every cloud provider, there are specific builder plugins that interact with the Cloud provider API to create the images.
In this block, we mention the builder plugin details. For example, if you want to build AWS AMI, you can specify amazon-ebs
as a builder plugin. You have to provide all the mandatory parameters associated with the amazon-ebs
builder plugin.
Also, In the following code block, you can see that we are calling ${var.ami_id}
declared in the variable block.
Check the official builder plugin documentation to know about the list of supported builders.
source "amazon-ebs" "httpd" {
ami_name = "PACKER-DEMO-${local.app_name}"
instance_type = "t2.micro"
region = "eu-west-1"
source_ami = "${var.ami_id}"
ssh_username = "ec2-user"
tags = {
Env = "DEMO"
Name = "PACKER-DEMO-${var.app_name}"
}
}
build
In this block, we declare all the provisioners and post-processor.
build {
sources = ["source.amazon-ebs.httpd"]
provisioner "shell" {
script = "script.sh"
}
}
Provisioner
The provisioner block is part of the build
block. Here you can mention how you want to configure the VM image. You can run shell scripts, Ansible playbooks, Check cookbooks, etc. See the provisioners page for all the supported provisioners.
The script or ansible-playbook
should be present in the folder where you have the Packer template.
provisioner "shell" {
script = "script.sh"
}
Also, there is a file provisioner using which you can copy files from the packer machine to the AMI.
For example,
provisioner "file"{
source = "/build/artifact/code.jar"
destination = "/app/code.jar"
}
Post-Processor
Post-Processor is not a mandatory block. In this block, we can specify what to do after creating the Image. For example, you can execute a local shell script to create a file with all the image details.
post-processor "shell-local" {
inline = ["echo foo"]
}
Also, you can use the manifest post-processor to get the AMI details in the manifest.json
file.
post-processor "manifest" {
output = "manifest.json"
strip_path = true
}
Creating AWS AMI Using Packer HCL Template
Now let’s see how to create an AMI using the Packer HCL template.
Note: I have given a basic example with minimal configurations just for your understanding. When it comes to Packer implementation in the real-time project, more customizations may be needed in terms of variables, naming schemes, builder plugging, etc.
Here is what the template does.
- Packer deploys an ec2 RHEL instance based on AMI id
ami-01e78c5619c5e68b4
in theeu-west-1
region. - Executes the shell script on the RHEL instance from the
script.sh
script provided in the template directory - Registers the AMI with the name given in the template.
- Deletes the running machine.
Let’s get our hands dirty with a practical example.
Step 1: Create a folder named “packer-vm
“.
mkdir packer-vm
Step 2: Inside the packer-vm
folder, create a file named “vm.pkr.hcl
” and copy the following HCL template.
If we put together all the blocks I explained in the above section, we get the following HCL Template.
variable "ami_id" {
type = string
default = "ami-01e78c5619c5e68b4"
}
variable "app_name" {
type = string
default = "httpd"
}
locals {
app_name = "httpd"
}
source "amazon-ebs" "httpd" {
ami_name = "PACKER-DEMO-${local.app_name}"
instance_type = "t2.micro"
region = "us-west-2"
source_ami = "${var.ami_id}"
ssh_username = "ec2-user"
tags = {
Env = "DEMO"
Name = "PACKER-DEMO-${var.app_name}"
}
}
build {
sources = ["source.amazon-ebs.httpd"]
provisioner "shell" {
script = "script/script.sh"
}
post-processor "shell-local" {
inline = ["echo foo"]
}
}
Step 3: Create a folder named script
inside the packer-vm
folder and cd into the script directory.
mkdir script && cd script
Step 3: Create a file name script.sh
and copy the following shell script to it. It just updates the yum repo and installs httpd
. Here, you can add all the shell scripts that you want to get executed during the AMI creation.
#!/bin/bash
sudo yum -y update
sudo yum install -y httpd
Step 4: [Very Important] Now that we have all the required files, let’s understand the required packer authentication.
If you are running Packer on your workstation for debugging or testing purposes, you need the AWS access and secret keys for Packer to interact with AWS services. Make sure you have the AWS access keys set on the file ~/.aws/credentials
.
Alternatively, you can pass AWS keys in the source block, as shown below. It is not a recommended practice. Accidently you might commit this to a source code repo.
source "amazon-ebs" "example" {
access_key = "ASDFASDFASDFJQWEKONNOASDF"
secret_key = "SDFQ324876134HJKLASDBJKASDJFHBAJSDFJKHASKDG"
}
If you are running Packer from an AWS ec2 instance, make sure you add an IAM role with permissions to interact with AWS ec2 services. I have added the required IAM policy JSON in Github. See Packer IAM policy JSON
Step 5: Now let’s validate the Packer HCL template before we build it. Validation is very important in the CI process.
packer validate vm.pkr.hcl
Step 6: Run the packer build using the following command.
packer build vm.pkr.hcl
The above command runs for almost 3 – 5 minutes.
If you check your AMI list, you should see an AMI named PACKER-DEMO-httpd
.
Debugging Packer Build
There are situations where you might need to debug a packer build.
The -debug flag lets you debug the AMI build.
For example,
packer build -debug vm.pkr.hcl
When you run packer in debug mode, You need to approve each packer step manually as shown in the image below.
Also, You can ssh into the ec2 instance created by Packer using the temporary ssh key or the ssh key pair provided in the packer config.
If you don’t specify the ssh key in the packer configuration, it creates a temporary ssh key and places it in the same folder where you run the packer command.
So that you can use the IP address and temporary ssh key to log in to the packer VM to debug the issues.
Packer Build Logs
Another way to debug the packer build is through packer detailed logs.
To get the build logs, you need to set the following environment variable before running the packer build command.
export PACKER_LOG=1
export PACKER_LOG_PATH="/path/to/packer.log"
Replace /path/to/packer.log
with the required log path. if you just specify packer.log, packer creates the log file in the folder where the Packer command is getting executed.
To persist this environment variable, add it to the $HOME/.bashrc
file or add it as s script in /etc/profile.d
folder.
Unit Testing Packer Templates
Packer templates should be treated as any other Infrastructure as code that involves testing.
You can write Packer tests using Check Inspec.
When implementing packer in production workflows, ensure you write the necessary tests as part of your CI/CD pipeline.
Check out this repo for packer inspec reference.
Packer Possible Errors
You might get the following ssh error when working on AWS-based AMIs. A possible cause of the issue is the support for ed25519
ssh key type for the temporary ssh key. To rectify this issue, you need to upgrade Packer to the latest version that supports ed25519
.
amazon-ebs: Error waiting for SSH: Packer experienced an authentication error when trying to connect via SSH. This can happen if your username/password are wrong. You may want to double-check your credentials as part of your debugging process.
Few Packer Tips
Tip 1: You can capture the output of the image ID in the manifest.json file. You can then parse the JSON file to get the exact image ID.
Tip 2: Use the -debug flag to log in to the server created by Packer for troubleshooting rather than running the packer build every time you have a change in provisioning scripts.
Conclusion
In this packer tutorial for beginners, I have covered the basics of image creation using Packer.
There are more customizations you can do in your Paker templates when it comes to a real-time project.
Again, it depends on what you want to achieve using packer.
You might be using Jenkins for image build pipelines or maybe custom utilities.
Either way, drop a comment and let me know how you manage images in your project.
Also, it is one of the devops tools that every devops engineer should know. I have explained this in my devops engineer skills guide.
18 comments
Hi,
I’m trying to use azure-arm build template for creating an image for azure, my packer is running on my container, Im trying to run the packer build code and got the below error. Could you please help me on this what I’m missing:
[1;32m==> azure-arm: Running builder …[0m
[1;31mBuild ‘azure-arm’ errored after 1 millisecond 814 microseconds: error fetching subscriptionID from VM metadata service for Managed Identity authentication: unexpected end of JSON input[0m
==> Wait completed after 1 millisecond 863 microseconds
==> Some builds didn’t complete successfully and had errors:
–> azure-arm: error fetching subscriptionID from VM metadata service for Managed Identity authentication: unexpected end of JSON input
==> Builds finished but no artifacts were created.
Excellent work. I tried this and it worked so well . As a beginner I could grasp the concept very well. Thanks a lot. Keep up your wonderful work. !
Hi, I am working on building Azure Golden Images of Linux Rhel and Ubuntu. I have used HCL File to build and it’s been successful, issue I am having now is that I need to create custom partitions for like /opt, /var/log, /opt/Prod etc. I need to know the exact way of using chroot_mounts to achieve this step please help if possible. Thanks
I have a working template with the ami name line equal to
“ami_name”: “tr-amazon-full-base-windows-2019-{{timestamp}}”,
I want instead to something like this instead
“ami_name”: “tr-amazon-windows-2022-full-{{formatdate(‘YYYY-MM-DD’T’hh:mm:ssZ’,timestamp)}}”,
but I get an error that says “formatdate command not defined”
Do you have any examples where I can use a date stamp that goes beyond epoch?
Hi Joe, one way is to convert the timestamp to the required format pass it as a variable during packer build.
will the new AMI gets saved in AWS ? how to control the permission of AMI ? like the image created is public or private etc ?
The AMIs will get stored in s3 which you won’t be able to access. However, under ec2 –> AMIs you can check the registered private AMIs. By default when you create an AMI, it will be in private. To make an AMI public, you need to explicitly change it to public in the AMI settings.
Where are the default values of {{.Vars}} , {{.Path}} taken by the file, if it is not declared in the packer template?
Can you please give some insights on the variables used in execute_command
You could try using
{{user `variable`}}
in execute_command and pass variable in a variable file or directly in command line.very nice
I believe this please needs to be reformatted
2>&1
as
2>&1
How To Run Ansible Playbook as Root User With Packer?
Hello there!
Could you please help me with variables declaration?
You say:
“`
First, you need to declare the variable name in the variable block as shown below.
“app_name”: “{{app_name_cmd_var}}”
“`
But packer validator throws the following error due to this:
“`
Error initializing core: error interpolating default value for ‘app_name’: template: root:1: function “app_name_cmd_var” not defined
“`
i got it to work! Pretty much need to make the userdata.txt to .sh and in the json file; add this:
“user_data_file”:”./userdata.sh”,
under builder
glad it worked!!
Wow, that was a fast reply! Thanks! I saw that in the documentation but there wasn’t an example for me to follow. However, I did create a txt file called userdata.txt with my instructions inside, but packer did not like the format. Here is the error output:
ubuntu@ip-172-10-0-199:/packer$ packer build -var-file=/packer/userdata2.txt no_creds.json
invalid value “/packer/userdata2.txt” for flag -var-file: Error reading variables in ‘/packer/userdata2.txt’: invalid character ‘#’ looking for beginning of value
Here is my userdata:
#!/bin/bash
#
touch /var/lock/subsys/local
/etc/init.d/syslog stop
update_root() {
if [ ! -d /root/.ssh ];then
mkdir /root/.ssh
chmod 700 /root/.ssh
curl http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key >> /root/.ssh/authorized_keys
chmod 400 /root/.ssh/authorized_keys
else
curl http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key >> /root/.ssh/authorized_keys
chmod 400 /root/.ssh/authorized_keys
fi
}
update_ssh(){
sed -i “s/PermitRootLogin.*/PermitRootLogin yes/” /etc/ssh/sshd_config
sed -i ‘/^Allow/ s/$/ root/’ /etc/ssh/sshd_config
sed -i “s/StrictModes.*/StrictModes no/” /etc/ssh/sshd_config
sed -i ‘s/GSSAPIAuthentication.*/GSSAPIAuthentication no/’ /etc/ssh/sshd_config
/sbin/service sshd restart
}
update_dns(){
cat > /etc/resolv.conf << EOL
search ec2.internal
options timeout:2 attempts:5
nameserver 172.10.0.2
EOL
}
update_services(){
/sbin/chkconfig –del vmware-tools
}
update_nss(){
/bin/cp /etc/nsswitch.conf.noldap /etc/nsswitch.conf
}
update_root
update_nss
update_dns
update_services
update_ssh
Thanks for the write-up! I was wondering how do you setup userdata for AWS in packer to run during bootup?
Hi Wai,
You can pass userdata as a string or as a file. Please refer the official documentation from here https://www.packer.io/docs/builders/amazon-ebs.html#user_data