Packer
Provision
In the previous tutorial, you created your first image with Packer. However, the image you built essentially repackaged an existing AMI. The real utility of Packer comes from automated provisioning in order to install and configure software in the machines prior to turning them into images.
Historically, pre-baked images have been frowned upon because changing them was so tedious and slow. Because Packer is completely automated (including provisioning) images can be changed quickly and integrated with modern configuration management tools such as Chef or Puppet.
In this tutorial, you will complete your image by installing Redis on it. Although pre-installing Redis to your AMI is a small example, it should give you an idea of what Packer provisioners can do.
Prerequisites
This tutorial assumes that you are continuing from the previous tutorials. If not, follow the steps below before continuing.
Install Packer
Create a directory named
packer_tutorial
and paste the following configuration into a file namedaws-ubuntu.pkr.hcl
.packer { required_plugins { amazon = { version = ">= 1.2.8" source = "github.com/hashicorp/amazon" } } } source "amazon-ebs" "ubuntu" { ami_name = "learn-packer-linux-aws" instance_type = "t2.micro" region = "us-west-2" source_ami_filter { filters = { name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*" root-device-type = "ebs" virtualization-type = "hvm" } most_recent = true owners = ["099720109477"] } ssh_username = "ubuntu" } build { name = "learn-packer" sources = [ "source.amazon-ebs.ubuntu" ] }
Initialize the Packer template.
$ packer init .
Once you have successfully initialized the template, you can continue with the rest of this tutorial.
Add provisioner to template
Using provisioners allows you to completely automate modifications to your image. You can use shell scripts, file uploads, and integrations with modern configuration management tools such as Chef or Puppet.
Find source.amazon-ebs.ubuntu
and note that uses the SSH communicator. By specifying the ssh_username
attribute, Packer is able to SSH into EC2 instance using a temporary keypair and security group to provision your instances.
source "amazon-ebs" "ubuntu" {
## ...
ssh_username = "ubuntu"
}
To write your first provisioner, add the following block into your Packer template, inside the build block and underneath the sources
assignment.
provisioner "shell" {
environment_vars = [
"FOO=hello world",
]
inline = [
"echo Installing Redis",
"sleep 30",
"sudo apt-get update",
"sudo apt-get install -y redis-server",
"echo \"FOO is $FOO\" > example.txt",
]
}
This block defines a shell
provisioner which sets an environment variable named FOO
in the shell execution environment and runs the commands in the inline
attribute. This provisioner will wait 30 seconds before installing Redis, then create a file named example.txt
that contains FOO is hello world
.
Your build block should look like the following.
build {
name = "learn-packer"
sources = [
"source.amazon-ebs.ubuntu"
]
provisioner "shell" {
environment_vars = [
"FOO=hello world",
]
inline = [
"echo Installing Redis",
"sleep 30",
"sudo apt-get update",
"sudo apt-get install -y redis-server",
"echo \"FOO is $FOO\" > example.txt",
]
}
}
Build image
AMI names must be unique. So you must update your AMI name before building the image.
Tip
The below snippet is formatted as a diff to give you context about
which parts of your configuration you need to change. Replace the content
displayed in red with the content displayed in green, leaving out the leading
+
and -
signs.
source "amazon-ebs" "ubuntu" {
- ami_name = "learn-packer-linux-aws"
+ ami_name = "learn-packer-linux-aws-redis"
## ...
}
Build the image with the provisioner.
$ packer build aws-ubuntu.pkr.hcl
learn-packer.amazon-ebs.ubuntu: output will be in this color.
==> learn-packer.amazon-ebs.ubuntu: Prevalidating any provided VPC information
==> learn-packer.amazon-ebs.ubuntu: Prevalidating AMI Name: learn-packer-linux-aws-redis
learn-packer.amazon-ebs.ubuntu: Found Image ID: ami-0dd273d94ed0540c0
==> learn-packer.amazon-ebs.ubuntu: Creating temporary keypair: packer_608adad6-ec17-5491-3a7f-f5f6babc7ded
==> learn-packer.amazon-ebs.ubuntu: Creating temporary security group for this instance: packer_608adad8-d501-4a4c-cbff-dae97ade08c6
==> learn-packer.amazon-ebs.ubuntu: Authorizing access to port 22 from [0.0.0.0/0] in the temporary security groups...
==> learn-packer.amazon-ebs.ubuntu: Launching a source AWS instance...
==> learn-packer.amazon-ebs.ubuntu: Adding tags to source instance
learn-packer.amazon-ebs.ubuntu: Adding tag: "Name": "Packer Builder"
learn-packer.amazon-ebs.ubuntu: Instance ID: i-0d5ce31654992b7c5
==> learn-packer.amazon-ebs.ubuntu: Waiting for instance (i-0d5ce31654992b7c5) to become ready...
==> learn-packer.amazon-ebs.ubuntu: Using ssh communicator to connect: 34.211.17.159
==> learn-packer.amazon-ebs.ubuntu: Waiting for SSH to become available...
==> learn-packer.amazon-ebs.ubuntu: Connected to SSH!
==> learn-packer.amazon-ebs.ubuntu: Provisioning with shell script: /var/folders/s6/m22_k3p11z104k2vx1jkqr2c0000gp/T/packer-shell294225062
learn-packer.amazon-ebs.ubuntu: Installing Redis
## ...
learn-packer.amazon-ebs.ubuntu: Setting up redis-tools (2:3.0.6-1ubuntu0.4) ...
learn-packer.amazon-ebs.ubuntu: Setting up redis-server (2:3.0.6-1ubuntu0.4) ...
learn-packer.amazon-ebs.ubuntu: Processing triggers for libc-bin (2.23-0ubuntu11.2) ...
learn-packer.amazon-ebs.ubuntu: Processing triggers for ureadahead (0.100.0-19.1) ...
learn-packer.amazon-ebs.ubuntu: Processing triggers for systemd (229-4ubuntu21.31) ...
==> learn-packer.amazon-ebs.ubuntu: Stopping the source instance...
learn-packer.amazon-ebs.ubuntu: Stopping instance
==> learn-packer.amazon-ebs.ubuntu: Waiting for the instance to stop...
==> learn-packer.amazon-ebs.ubuntu: Creating AMI learn-packer-linux-aws-redis from instance i-0d5ce31654992b7c5
learn-packer.amazon-ebs.ubuntu: AMI: ami-0439819e795630f08
==> learn-packer.amazon-ebs.ubuntu: Waiting for AMI to become ready...
==> learn-packer.amazon-ebs.ubuntu: Terminating the source AWS instance...
==> learn-packer.amazon-ebs.ubuntu: Cleaning up any extra volumes...
==> learn-packer.amazon-ebs.ubuntu: No volumes to clean up, skipping
==> learn-packer.amazon-ebs.ubuntu: Deleting temporary security group...
==> learn-packer.amazon-ebs.ubuntu: Deleting temporary keypair...
Build 'learn-packer.amazon-ebs.ubuntu' finished after 3 minutes 41 seconds.
==> Wait completed after 3 minutes 41 seconds
==> Builds finished. The artifacts of successful builds are:
--> learn-packer.amazon-ebs.ubuntu: AMIs were created:
us-west-2: ami-0439819e795630f08
In the output, you will find the Provisioning with shell script
that confirms that the Packer ran the provision step. Notice how Packer also outputs the first inline command (Installing Redis
).
Visit the AWS AMI page to verify that Packer successfully built your AMI.
Add more provisioners
The shell provisioner demonstrated above is extremely powerful and flexible. For complex provisioning, you can pass entire shell scripts rather than the inline
declarations shown above.
You can run as many provisioners as you'd like. Provisioners run in the order they are declared.
Replace your existing build block with the following. This adds a provisioner to the end of your build block that will print a message in the shell execution environment.
build {
name = "learn-packer"
sources = [
"source.amazon-ebs.ubuntu"
]
provisioner "shell" {
environment_vars = [
"FOO=hello world",
]
inline = [
"echo Installing Redis",
"sleep 30",
"sudo apt-get update",
"sudo apt-get install -y redis-server",
"echo \"FOO is $FOO\" > example.txt",
]
}
provisioner "shell" {
inline = ["echo This provisioner runs last"]
}
}
Build and verify updated image
Update your AMI name before building the image.
source "amazon-ebs" "ubuntu" {
- ami_name = "learn-packer-linux-aws-redis"
+ ami_name = "learn-packer-linux-aws-redis-msg"
## ...
}
Then build the image again.
$ packer build aws-ubuntu.pkr.hcl
learn-packer.amazon-ebs.ubuntu: output will be in this color.
==> learn-packer.amazon-ebs.ubuntu: Prevalidating any provided VPC information
==> learn-packer.amazon-ebs.ubuntu: Prevalidating AMI Name: learn-packer-linux-aws-redis-msg
learn-packer.amazon-ebs.ubuntu: Found Image ID: ami-0dd273d94ed0540c0
## ...
==> learn-packer.amazon-ebs.ubuntu: Provisioning with shell script: /var/folders/s6/m22_k3p11z104k2vx1jkqr2c0000gp/T/packer-shell254608130
learn-packer.amazon-ebs.ubuntu: Installing Redis
## ...
==> learn-packer.amazon-ebs.ubuntu: Provisioning with shell script: /var/folders/s6/m22_k3p11z104k2vx1jkqr2c0000gp/T/packer-shell824844471
learn-packer.amazon-ebs.ubuntu: This provisioner runs last
## ...
==> Wait completed after 3 minutes 50 seconds
==> Builds finished. The artifacts of successful builds are:
--> learn-packer.amazon-ebs.ubuntu: AMIs were created:
us-west-2: ami-0fc28cd2cda61eb14
Notice how there are two Provisioning with shell-script
executions. The second provisioning step displays the expected message.
Visit the AWS AMI page to verify that Packer successfully built your AMI.
Next steps
In this tutorial, you used provisioners to preinstall Redis and create a file in your AMI. You can apply these same principles to provisioning and configuring any Packer images. Continue to the next tutorial to make your Packer template more robust with variables.
Refer to the following resources for additional details on the concepts covered in this tutorial:
- Read more about the Packer provisioners.
- Learn more about how to use Packer provisioner blocks.
- Learn how to use Terraform and Packer to provision an EC2 instance with your SSH keys by following the Provision Infrastructure with Packer tutorial.