Writing Skills
writing-skills
效率与协作技能,来自 obra/superpowers,查看原文获取完整用途和操作说明。
packer【部署与运维】HashiCorp Packer builds identical machine images (AWS AMIs, Google Compute images, Docker images and more) from one HCL template, with provisioners that configure the image and plugins for each platform. Use when the user wants to bake an AMI or golden image, build a Docker image with provisioners, write a .pkr.hcl template, or fix packer init, valid…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Packer reads a template (HCL2, files ending in .pkr.hcl), launches a temporary machine or container from a base image with a source (builder), runs provisioners against it, then saves the result as an image and runs post-processors. Builders live in plugins that packer init downloads, so a template must declare the plugins it needs.
Facts to keep in mind (Packer 1.16.1, September 2026):
packer hcl2_upgrade.provenance post-processor (SLSA attestations), a continue_on_error argument on provisioner blocks, and packer verify-attestation.brew tap hashicorp/tap && brew install hashicorp/tap/packer # macOS / Linux with Homebrew
# Debian/Ubuntu: HashiCorp's apt repository
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /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
packer version
A manual zip from releases.hashicorp.com must be checked against the packer_<version>_SHA256SUMS file before unzipping (grep linux_amd64.zip packer_1.16.1_SHA256SUMS | sha256sum -c).
packer init . # install the plugins named in required_plugins (do this first)
packer fmt . # canonical formatting (-check for CI)
packer validate . # syntax and argument check; some plugins also need their tools or credentials
packer build . # run every build in the directory
Useful flags: -var "app_version=2.4.0", -var-file=prod.pkrvars.hcl, -only='amazon-ebs.ubuntu', -except=..., -on-error=ask (keep the failed instance for debugging), -parallel-builds=1, and PACKER_LOG=1 for debug logs. Variables can also come from PKR_VAR_<name> environment variables. packer init -upgrade . moves plugins to the newest version allowed by the constraints.
Request: "Bake an AMI with Nginx and our config for us-east-1."
# aws-web.pkr.hcl
packer {
required_plugins {
amazon = {
version = ">= 1.8.0"
source = "github.com/hashicorp/amazon"
}
}
}
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "app_version" {
type = string
}
source "amazon-ebs" "ubuntu" {
ami_name = "web-${var.app_version}-{{timestamp}}"
instance_type = "t3.small"
region = var.aws_region
source_ami_filter {
filters = {
name = "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"] # Canonical
}
ssh_username = "ubuntu"
tags = {
Name = "web-${var.app_version}"
Builder = "packer"
}
}
build {
sources = ["source.amazon-ebs.ubuntu"]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx curl jq",
"sudo systemctl enable nginx",
]
}
provisioner "file" {
source = "files/nginx.conf"
destination = "/tmp/nginx.conf"
}
provisioner "shell" {
inline = [
"sudo mv /tmp/nginx.conf /etc/nginx/sites-available/default",
"sudo nginx -t",
"sudo apt-get clean && sudo rm -rf /var/lib/apt/lists/*",
]
}
post-processor "manifest" {
output = "manifest.json"
strip_path = true
}
}
export AWS_PROFILE=image-builder # credentials come from the standard AWS chain
packer init . && packer build -var "app_version=2.4.0" .
Result: Packer launches a t3.small, provisions it, registers the AMI and terminates the instance; manifest.json lists the new AMI ID per region. The Canonical owner ID 099720109477 is the real Ubuntu publisher, so keep the owners filter to avoid picking a lookalike image.
Request: "Build a Python app image with Packer and tag it for our registry."
packer {
required_plugins {
docker = {
version = ">= 1.1.0"
source = "github.com/hashicorp/docker"
}
}
}
variable "app_version" {
type = string
default = "2.4.0"
}
source "docker" "app" {
image = "python:3.13-slim"
commit = true
changes = [
"EXPOSE 8080",
"WORKDIR /app",
"CMD [\"python\", \"app.py\"]",
]
}
build {
sources = ["source.docker.app"]
provisioner "shell" {
inline = ["mkdir -p /app", "pip install --no-cache-dir flask gunicorn"]
}
provisioner "file" {
source = "app/"
destination = "/app/"
}
post-processors {
post-processor "docker-tag" {
repository = "registry.acme-labs.io/web/api"
tags = ["latest", var.app_version]
}
post-processor "docker-push" {} # uses the existing `docker login` credentials
}
}
Result: the image registry.acme-labs.io/web/api:2.4.0 (and latest) exists locally and is pushed. Put docker-tag and docker-push in one post-processors block so they run as a chain; separate blocks run independently from the original artifact. Packer is not a Dockerfile replacement: use it when you want the same provisioners (shell, Ansible) for containers and VMs.
packer {
required_plugins {
googlecompute = {
version = ">= 1.2.0"
source = "github.com/hashicorp/googlecompute"
}
ansible = {
version = ">= 1.1.0"
source = "github.com/hashicorp/ansible"
}
}
}
variable "gcp_project" {
type = string
}
source "googlecompute" "ubuntu" {
project_id = var.gcp_project
source_image_family = "ubuntu-2404-lts-amd64"
zone = "us-central1-a"
machine_type = "e2-medium"
ssh_username = "packer"
image_name = "web-{{timestamp}}"
image_family = "web"
}
build {
sources = ["source.googlecompute.ubuntu"]
provisioner "shell" {
scripts = ["scripts/base-setup.sh", "scripts/install-app.sh"]
environment_vars = ["APP_VERSION=2.4.0"]
}
provisioner "ansible" {
playbook_file = "ansible/configure.yml"
}
}
Result: an image in the web family is created in the project. List several sources in sources (for example an amazon-ebs one) and the same provisioners run once per source; -only='googlecompute.ubuntu' builds just one. Pinning source_image_family picks the latest image in the family instead of a dated name that is eventually deprecated. The ansible provisioner needs the ansible-playbook binary on the machine running Packer; packer validate fails with "executable file not found" otherwise.
packer init after changing required_plugins; "unknown source type" or "plugin not found" errors almost always mean it was skipped.PKR_VAR_* variables, and sensitive = true on secret variables.version = ">= x.y.z" and test upgrades.packer validate (syntax only with -syntax-only) and, for Docker, a local build.packer build -on-error=ask to inspect a failed machine, and PACKER_LOG=1 PACKER_LOG_PATH=packer.log for details.