HashiCorp Packer Machine Images Introduction
- Installing Packer
- Configuring ZSH (Optional)
- Installing Packer Plugins
- Packer Builders
- Packer Communicator
HashiCorp Packer automates the creation of any type of machine image. It embraces modern configuration management by encouraging you to use automated scripts to install and configure the software within your Packer-made images. Packer brings machine images into the modern age, unlocking untapped potential and opening new opportunities.
Installing Packer
I am going to install Packer manually on an Arch LINUX Desktop using the pre-compiled binary. Download the ZIP file to your Download directory and unzip it.
Print a colon-separated list of locations in your PATH
:
echo $PATH
Move the Packer binary to one of the listed locations. This command assumes that the binary is currently in your downloads folder and that your PATH
includes /usr/local/bin
, but you can customize it if your locations are different:
mv ~/Downloads/packer /usr/local/bin/
After installing Packer, verify the installation worked by opening a new command prompt or console, and checking that packer is available:
packer
usage: packer [--version] [--help] <command> [<args>]
Available commands are:
build build image(s) from template
fix fixes templates from old versions of packer
inspect see components of a template
validate check that a template is valid
version Prints the Packer version
Configuring ZSH (Optional)
To install Packer autocompletion in oh-my-zsh
clone this repo:
git clone https://github.com/gunzy83/packer-zsh-completion.git ~/.oh-my-zsh/plugins/packer
Then add the plugin to your plugin list in oh-my-zsh configuration:
nano ~/.zshrc
plugins=(... packer)
After installation re-source your .zshrc
:
source .zshrc
Installing Packer Plugins
Installing Golang
Packer as well as it's plugins are written in Go. To install a plugin from source make sure that you have go & go-tools installed:
sudo pacman -S go
go version
go version go1.15.2 linux/amd64
You can check that Go is installed correctly by building a simple program hello.go
, as follows:
package main
import "fmt"
func main() {
fmt.Println("Hello, Terminal!")
}
Then run it with the go tool:
go run ./hello.go
Hello, Terminal!
Building a Packer Plugin from Source
Start by cloning the repository that you want to use and enter the directory, e.g. :
git clone https://github.com/SwampDragons/packer-provisioner-comment.git
cd ./packer-provisioner-comment
Now install all Go dependencies the application needs and run the build command to create the binary that we can install in Packer:
go mod download
go build
This will output a binary file called main
inside the directory. We can rename it and move it either into /usr/local/bin/
to have it available globally. But to keep our system clean we can also copy it into our users home directory:
mkdir -p ~/.packer.d/plugins
mv main ~/.packer.d/plugins/packer-provisioner-comment
Packer Builders
In the builders
section we need to define the platform that want to deploy to and add the necessary configuration like API keys and source images.
Docker Builder
The Docker Packer builder builds Docker images using Docker. The builder starts a Docker container, runs provisioners within this container, then exports the container for reuse or commits the image. Packer builds Docker containers without the use of Dockerfiles. The Docker builder must run on a machine that has Docker Engine installed.
Basic Example: Export
Below is a fully functioning example. It doesn't do anything useful, since no provisioners are defined, but it will effectively repackage an image.
{
"builders": [
{
"export_path": "ubuntu.tar",
"image": "ubuntu",
"type": "docker"
}
]
}
Validating and Running your Template File
packer validate ./packer.json
packer fix ./packer.json
packer build packer.json
docker: output will be in this color.
==> docker: Creating a temporary directory for sharing data...
==> docker: Pulling Docker image: ubuntu
docker: Using default tag: latest
docker: latest: Pulling from library/ubuntu
docker: Digest: sha256:fff16eea1a8ae92867721d90c59a75652ea66d29c05294e6e2f898704bdb8cf1
docker: Status: Image is up to date for ubuntu:latest
docker: docker.io/library/ubuntu:latest
==> docker: Starting docker container...
docker: Run command: docker run -v /home/user/.packer.d/tmp926764499:/packer-files -d -i -t --entrypoint=/bin/sh -- ubuntu
docker: Container ID: adcc271a70e326726d1fdf48e256e437c0abc1644749a86a87c4550860319ae9
==> docker: Using docker communicator to connect: 172.17.0.2
==> docker: Exporting the container
==> docker: Killing the container: adcc271a70e326726d1fdf48e256e437c0abc1644749a86a87c4550860319ae9
Build 'docker' finished after 6 seconds 890 milliseconds.
==> Wait completed after 6 seconds 890 milliseconds
==> Builds finished. The artifacts of successful builds are:
--> docker: Exported Docker file: ubuntu.tar
Basic Example: Commit
Below is another example, the same as above but instead of exporting the running container, this one commits the container to an image. The image can then be more easily tagged, pushed, etc.
{
"builders": [
{
"commit": true,
"image": "ubuntu",
"type": "docker"
}
]
}
Basic Example: Changes to Metadata
Below is an example using the changes argument of the builder. This feature allows the source images metadata to be changed when committed back into the Docker environment. It is derived from the docker commit --change command line option to Docker.
Example uses of all of the options, assuming one is building an NGINX image from ubuntu as an simple example:
{
"builders": [
{
"changes": [
"USER www-data",
"WORKDIR /var/www",
"ENV HOSTNAME www.example.com",
"VOLUME /test1 /test2",
"EXPOSE 80 443",
"LABEL version=1.0",
"ONBUILD RUN date",
"CMD [\"nginx\", \"-g\", \"daemon off;\"]",
"ENTRYPOINT /var/www/start.sh"
],
"commit": true,
"image": "ubuntu",
"type": "docker"
}
]
}
Building and Committing your Image
packer build packer.json
docker: output will be in this color.
==> docker: Creating a temporary directory for sharing data...
==> docker: Pulling Docker image: ubuntu
docker: Using default tag: latest
docker: latest: Pulling from library/ubuntu
docker: Digest: sha256:fff16eea1a8ae92867721d90c59a75652ea66d29c05294e6e2f898704bdb8cf1
docker: Status: Image is up to date for ubuntu:latest
docker: docker.io/library/ubuntu:latest
==> docker: Starting docker container...
docker: Run command: docker run -v /home/user/.packer.d/tmp556583891:/packer-files -d -i -t --entrypoint=/bin/sh -- ubuntu
docker: Container ID: c5126f38aa32ff58fe3ce11622be59a9f6216c4478d93e34719f89e1c522f2ac
==> docker: Using docker communicator to connect: 172.17.0.2
==> docker: Committing the container
docker: Image ID: sha256:e186c58d2da00ec11c2b273941b6c61142fcdaae3d2949e9135278fc36615713
==> docker: Killing the container: c5126f38aa32ff58fe3ce11622be59a9f6216c4478d93e34719f89e1c522f2ac
Build 'docker' finished after 11 seconds 495 milliseconds.
==> Wait completed after 11 seconds 495 milliseconds
==> Builds finished. The artifacts of successful builds are:
--> docker: Imported Docker image: sha256:e186c58d2da00ec11c2b273941b6c61142fcdaae3d2949e9135278fc36615713
docker images
REPOSITORY | TAG | IMAGE ID | CREATED | SIZE |
none | none | e186c58d2da0 | About a minute ago | 72.9MB |
Packer Communicator
By default Packer uses SSH to create the machine from your template. If you need to use another communicator, e.g. WinRM on Windows images, those have to be defined.
The communicator needs to be configured with the default user login to the source image that you want to use, e.g. :
{
...
"ssh_username": "ubuntu",
"ssh_password": "ubuntu"
}