In order to create an image with packer, you need to define a template that configures your image. Here' a simple example:
source "amazon-ebs" "example" {
access_key = var.aws_access_key
secret_key = var.aws_secret_key
region = var.region
source_ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 AMI
instance_type = "t2.micro"
ssh_username = "ec2-user"
ami_name = "packer-example {{timestamp}}"
}
build {
sources = ["source.amazon-ebs.example"]
provisioner "shell" {
script = "provision.sh"
}
}
This template references a file called provision.sh
. This is useful to do
additional configuration on the image before it's created. For the sake of
example here's what a provision shell file could look like:
#!/bin/bash
echo "Hello, World!" > /tmp/hello.txt
sudo yum update -y
sudo yum install -y httpd
sudo systemctl enable httpd
sudo systemctl start httpd
Let's say we have both files under the same directory:
.
├── provision.sh
└── template.pkr.hcl
In order to create an image you need to run the build
command. When running
it, you need to inform packer how to find the template. Assuming you're running
packer build
in the directory you have your template, you can do that in 2
ways:
packer build template.pkr.hcl
or
packer build .
where .
is the current directory.
This is all well and simple, but things start getting a bit confusing when you start nesting directories.
Let's say you have the following directory structure:
.
├── image
│ ├── provision.sh
│ └── template.pkr.hcl
if you now run the command from the parent directory:
% packer build image/template.pkr.hcl
Error: Failed preparing provisioner-block "shell" ""
on image/template.pkr.hcl line 44:
(source code not available)
1 error(s) occurred:
* Bad script 'provision.sh': stat provision.sh: no such file or directory
Packer can't find the provision script. This is because packer is looking for
the provision.sh
file in the directory where you're running the command, not
where the template is.
You could be tempted to fix this by modifying the path to the provision script so that it's relative to wherever you're running your command from, but that makes your script less portable by imbuing it with your current structure.
My usual hacky way to fix this is to simply cd
into the directory where the
packer image is before running the command. I do that between parentheses so
that once the script is ran my shell goes back to the original directory:
(cd image && packer build template.pkr.hcl)
While writing this I learned that you can also solve it by using HCL path
variables.
Specifically, you can use the path.root
variable to reference the directory
where the template is.
provisioner "shell" {
scripts = [
"${path.root}/provision.sh"
]
}
Honestly, while this could seem more elegant, it seems less clear to me. Unless
you know what path.root
is, it's not immediately clear what it's doing.
Even reading the documentation:
path.root: the directory of the input HCL file or the input folder.
it still takes me a few reads to understand what it is.
So yep, I'm happy I found a more official way to solve this issue, but I will humbly stick to my hacky way, as I suspect it looks clearer to a bigger percentage of devs.