Configure Packer
Atmos natively supports HashiCorp Packer and lets you create identical machine images for multiple platforms from a single source template using the power of Atmos components, stacks, imports, inheritance, templating and YAML functions. It's compatible with every version of Packer and designed to work with multiple different versions of it concurrently.
You will learn
- How to configure Atmos to use Packer to build machine images
- Example Packer and Atmos configurations to build an AWS bastion AMI from an Amazon Linux 2023 base image
Keep in mind that Atmos does not handle the downloading or installation of Packer; it assumes that any required binaries for the commands are already installed on your system.
CLI Configuration (atmos.yaml
)
atmos.yaml
components:
packer:
# The executable to be called by Atmos when running Packer commands
command: "packer" # or `/usr/bin/packer`
# Can also be set using 'ATMOS_COMPONENTS_PACKER_BASE_PATH' ENV var, or '--packer-dir' command-line argument
# Supports both absolute and relative paths
base_path: "components/packer"
components.packer.command
- The executable to be called by Atmos when running Packer commands
components.packer.base_path
- The root directory where the Packer components and configurations are located. This path serves as the starting point for resolving any relative paths within the Packer setup.
Stack Configuration for Components
Settings for Packer can also be specified in Atmos stack configurations, where they are compatible with inheritance. This feature allows projects to tailor behavior according to individual component needs.
While defaults for everything are defined in the atmos.yaml
, the same settings can be overridden by Stack configurations at any level:
packer
components.packer
components.packer._component_
For instance, you can modify the command executed for a specific component by overriding the command
parameter.
atmos.yaml
components:
packer:
bastion:
# Use Packer v1.14.1 to provision the `bastion` component
command: "/usr/local/bin/packer-1.14.1"
Example: Configure and Provision a Packer Component with Atmos
Configure Packer in atmos.yaml
atmos.yaml
base_path: "./"
components:
packer:
# Can also be set using 'ATMOS_COMPONENTS_PACKER_COMMAND' ENV var, or '--packer-command' command-line argument
command: packer
# Can also be set using 'ATMOS_COMPONENTS_PACKER_BASE_PATH' ENV var, or '--packer-dir' command-line argument
base_path: "components/packer"
stacks:
base_path: "stacks"
included_paths:
- "deploy/**/*"
excluded_paths:
- "**/_defaults.yaml"
name_template: "{{ .vars.stage }}"
logs:
file: "/dev/stderr"
level: Info
# `Go` templates in Atmos manifests
# https://atmos.tools/core-concepts/stacks/templates
templates:
settings:
enabled: true
evaluations: 1
# https://masterminds.github.io/sprig
sprig:
enabled: true
# https://docs.gomplate.ca
gomplate:
enabled: true
timeout: 10
# https://docs.gomplate.ca/datasources
datasources: {}
Add Packer template (Packer component)
components/packer/aws/bastion/main.pkr.hcl
# https://developer.hashicorp.com/packer/docs/templates/hcl_templates/blocks/source
# https://developer.hashicorp.com/packer/integrations/hashicorp/amazon/latest/components/builder/ebs
# https://developer.hashicorp.com/packer/integrations/hashicorp/amazon
# https://developer.hashicorp.com/packer/integrations/hashicorp/amazon#authentication
# https://developer.hashicorp.com/packer/tutorials/docker-get-started/docker-get-started-post-processors
# https://developer.hashicorp.com/packer/tutorials/aws-get-started
packer {
required_plugins {
# https://developer.hashicorp.com/packer/integrations/hashicorp/amazon
amazon = {
source = "github.com/hashicorp/amazon"
version = "~> 1"
}
}
}
variable "region" {
type = string
description = "AWS Region"
}
variable "stage" {
type = string
default = null
}
variable "ami_org_arns" {
type = list(string)
description = "List of Amazon Resource Names (ARN) of AWS Organizations that have access to launch the resulting AMI(s). By default no organizations have permission to launch the AMI"
default = []
}
variable "ami_ou_arns" {
type = list(string)
description = "List of Amazon Resource Names (ARN) of AWS Organizations organizational units (OU) that have access to launch the resulting AMI(s). By default no organizational units have permission to launch the AMI."
default = []
}
variable "ami_users" {
type = list(string)
description = "List of account IDs that have access to launch the resulting AMI(s). By default no additional users other than the user creating the AMI has permissions to launch it."
default = []
}
variable "kms_key_arn" {
type = string
description = "KMS Key ARN"
}
variable "instance_type" {
type = string
description = "Instance type"
}
variable "volume_size" {
type = number
description = "Volume size"
}
variable "volume_type" {
type = string
description = "Volume type"
}
variable "ami_name" {
type = string
description = "AMI name"
}
variable "source_ami" {
type = string
description = "Source AMI"
}
variable "ssh_username" {
type = string
description = "Instance type"
}
variable "encrypt_boot" {
type = bool
description = "Encrypt boot"
}
variable "skip_create_ami" {
type = bool
description = "If true, Packer will not create the AMI. Useful for setting to true during a build test stage"
}
variable "ami_tags" {
type = map(string)
description = "AMI tags"
}
# https://developer.hashicorp.com/packer/integrations/hashicorp/amazon#authentication
variable "assume_role_arn" {
type = string
description = "Amazon Resource Name (ARN) of the IAM Role to assume. Refer to https://developer.hashicorp.com/packer/integrations/hashicorp/amazon#authentication"
}
variable "assume_role_session_name" {
type = string
description = "Assume role session name"
}
variable "assume_role_duration_seconds" {
type = number
description = "Assume role duration seconds"
}
variable "manifest_file_name" {
type = string
description = "Manifest file name. Refer to https://developer.hashicorp.com/packer/docs/post-processors/manifest"
}
variable "manifest_strip_path" {
type = bool
description = "Manifest strip path. Refer to https://developer.hashicorp.com/packer/docs/post-processors/manifest"
}
variable "associate_public_ip_address" {
type = bool
description = "If this is `true`, the new instance will get a Public IP"
}
variable "provisioner_shell_commands" {
type = list(string)
description = "List of commands to execute on the machine that Packer builds"
default = []
}
variable "force_deregister" {
type = bool
description = "Force Packer to first deregister an existing AMI if one with the same name already exists"
default = false
}
variable "force_delete_snapshot" {
type = bool
description = "Force Packer to delete snapshots associated with AMIs, which have been deregistered by `force_deregister`"
default = false
}
source "amazon-ebs" "al2023" {
ami_name = var.ami_name
source_ami = var.source_ami
instance_type = var.instance_type
region = var.region
ssh_username = var.ssh_username
ami_org_arns = var.ami_org_arns
ami_ou_arns = var.ami_ou_arns
ami_users = var.ami_users
kms_key_id = var.kms_key_arn
encrypt_boot = var.encrypt_boot
force_deregister = var.force_deregister
force_delete_snapshot = var.force_delete_snapshot
associate_public_ip_address = var.associate_public_ip_address
ami_block_device_mappings {
device_name = "/dev/xvda"
volume_size = var.volume_size
volume_type = var.volume_type
delete_on_termination = true
}
assume_role {
role_arn = var.assume_role_arn
session_name = var.assume_role_session_name
duration_seconds = var.assume_role_duration_seconds
}
aws_polling {
delay_seconds = 5
max_attempts = 100
}
tags = var.ami_tags
}
build {
sources = ["source.amazon-ebs.al2023"]
provisioner "shell" {
inline = var.provisioner_shell_commands
}
# https://developer.hashicorp.com/packer/tutorials/docker-get-started/docker-get-started-post-processors
# https://developer.hashicorp.com/packer/docs/post-processors
# https://developer.hashicorp.com/packer/docs/post-processors/manifest
post-processor "manifest" {
output = var.manifest_file_name
strip_path = var.manifest_strip_path
}
}
Configure defaults for the Packer component in the catalog
stacks/catalog/aws/bastion/defaults.yaml
# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json
components:
packer:
aws/bastion:
settings:
packer:
template: "main.pkr.hcl"
source_ami: "ami-0013ceeff668b979b"
source_ami_name: "al2023-ami-2023.7.20250527.1-kernel-6.12-arm64"
source_ami_description: "Amazon Linux 2023 AMI 2023.7.20250527.1 arm64 HVM kernel-6.12"
source_ami_owner_account_id: "137112412989"
region: "us-east-2"
org_id: "o-xxxxxxxxx"
org_management_account_id: "xxxxxxxxxxxx"
metadata:
component: aws/bastion
vars:
# https://masterminds.github.io/sprig/date.html
ami_name: "bastion-al2023-{{ now | unixEpoch }}"
source_ami: "{{ .settings.packer.source_ami }}"
region: "{{ .settings.packer.region }}"
ami_org_arns:
- "arn:aws:organizations::{{ .settings.packer.org_management_account_id }}:organization/{{ .settings.packer.org_id }}"
ami_ou_arns: []
ami_users: []
kms_key_arn: null
encrypt_boot: false
ssh_username: "ec2-user"
associate_public_ip_address: true
volume_type: "gp3"
skip_create_ami: false
manifest_file_name: "manifest.json"
manifest_strip_path: false
assume_role_session_name: "atmos-packer"
assume_role_duration_seconds: 1800
force_deregister: false
force_delete_snapshot: false
# SSM Agent is pre-installed on AL2023 AMIs but should be enabled explicitly as done above.
# `dnf clean all` removes cached metadata and packages to reduce AMI size.
# `cloud-init clean` ensures the image will boot as a new instance on the next launch.
provisioner_shell_commands:
# Enable and start the SSM agent (already installed by default on AL2023)
- "sudo systemctl enable --now amazon-ssm-agent"
# Install packages, clean metadata and cloud-init
- "sudo -E bash -c 'dnf install -y jq && dnf clean all && cloud-init clean'"
# Install other packages
ami_tags:
SourceAMI: "{{ .settings.packer.source_ami }}"
SourceAMIName: "{{ .settings.packer.source_ami_name }}"
SourceAMIDescription: "{{ .settings.packer.source_ami_description }}"
SourceAMIOwnerAccountId: "{{ .settings.packer.source_ami_owner_account_id }}"
ScanStatus: pending
Define Atmos nonprod
and prod
stacks
stacks/deploy/nonprod.yaml
# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json
vars:
stage: nonprod
import:
- catalog/aws/bastion/defaults
components:
packer:
aws/bastion:
vars:
# Define the variables specific to the `nonprod` account
instance_type: "t4g.small"
volume_size: 8
assume_role_arn: "arn:aws:iam::NONPROD_ACCOUNT_ID:role/ROLE_NAME"
ami_tags:
Stage: nonprod
stacks/deploy/prod.yaml
# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json
vars:
stage: prod
import:
- catalog/aws/bastion/defaults
components:
packer:
aws/bastion:
vars:
# Define the variables specific to the `prod` account
instance_type: "t4g.medium"
volume_size: 16
assume_role_arn: "arn:aws:iam::PROD_ACCOUNT_ID:role/ROLE_NAME"
ami_tags:
Stage: prod
Execute Atmos Packer commands
atmos packer version
> atmos packer version
Packer v1.14.1
atmos packer validate aws/bastion -s nonprod
# https://developer.hashicorp.com/packer/docs/commands/validate
> atmos packer validate aws/bastion -s nonprod
The configuration is valid.
atmos packer inspect aws/bastion -s nonprod
# https://developer.hashicorp.com/packer/docs/commands/inspect
> atmos packer inspect aws/bastion -s nonprod
Packer Inspect: HCL2 mode
> input-variables:
var.ami_name: "bastion-al2023-1754457104"
var.ami_org_arns: "[\n \"arn:aws:organizations::xxxxxxxxxxxx:organization/o-xxxxxxxxx\",\n]"
var.ami_ou_arns: "[]"
var.ami_tags: "{\n \"ScanStatus\" = \"pending\"\n \"SourceAMI\" = \"ami-0013ceeff668b979b\"\n \"SourceAMIDescription\" = \"Amazon Linux 2023 AMI 2023.7.20250527.1 arm64 HVM kernel-6.12\"\n \"SourceAMIName\" = \"al2023-ami-2023.7.20250527.1-kernel-6.12-arm64\"\n \"SourceAMIOwnerAccountId\" = \"137112412989\"\n \"Stage\" = \"nonprod\"\n}"
var.ami_users: "[]"
var.associate_public_ip_address: "true"
var.assume_role_arn: "null"
var.assume_role_duration_seconds: "1800"
var.assume_role_session_name: "atmos-packer"
var.encrypt_boot: "false"
var.force_delete_snapshot: "false"
var.force_deregister: "false"
var.instance_type: "t4g.small"
var.kms_key_arn: "null"
var.manifest_file_name: "manifest.json"
var.manifest_strip_path: "false"
var.provisioner_shell_commands: "[\n \"sudo systemctl enable --now amazon-ssm-agent\",\n \"sudo -E bash -c 'dnf install -y jq && dnf clean all && cloud-init clean'\",\n]"
var.region: "us-east-2"
var.skip_create_ami: "false"
var.source_ami: "ami-0013ceeff668b979b"
var.ssh_username: "ec2-user"
var.stage: "nonprod"
var.volume_size: "8"
var.volume_type: "gp3"
> local-variables:
> builds:
> <0>:
sources:
amazon-ebs.al2023
provisioners:
shell
post-processors:
0:
manifest
atmos packer init aws/bastion -s nonprod
# https://developer.hashicorp.com/packer/docs/commands/init
> atmos packer init aws/bastion -s nonprod
Installed plugin github.com/hashicorp/amazon v1.3.9 in "~/.config/packer/plugins/github.com/hashicorp/amazon/packer-plugin-amazon_v1.3.9_x5.0_darwin_arm64"
atmos packer build aws/bastion -s nonprod
# https://developer.hashicorp.com/packer/docs/commands/build
> atmos packer build aws/bastion -s nonprod
amazon-ebs.al2023:
==> amazon-ebs.al2023: Prevalidating any provided VPC information
==> amazon-ebs.al2023: Prevalidating AMI Name: bastion-al2023-1754025080
==> amazon-ebs.al2023: Found Image ID: ami-0013ceeff668b979b
==> amazon-ebs.al2023: Setting public IP address to true on instance without a subnet ID
==> amazon-ebs.al2023: No VPC ID provided, Packer will use the default VPC
==> amazon-ebs.al2023: Inferring subnet from the selected VPC "vpc-xxxxxxx"
==> amazon-ebs.al2023: Set subnet as "subnet-xxxxxxx"
==> amazon-ebs.al2023: Creating temporary keypair: packer_688c4c79-f14a-b77e-ca1e-b5b4c17b4581
==> amazon-ebs.al2023: Creating temporary security group for this instance: packer_688c4c7b-3f16-69f9-0c39-88a3fcbe94fd
==> amazon-ebs.al2023: Authorizing access to port 22 from [0.0.0.0/0] in the temporary security groups...
==> amazon-ebs.al2023: Launching a source AWS instance...
==> amazon-ebs.al2023: changing public IP address config to true for instance on subnet "subnet-xxxxxxx"
==> amazon-ebs.al2023: Instance ID: i-0b621ca091aa4c240
==> amazon-ebs.al2023: Waiting for instance (i-0b621ca091aa4c240) to become ready...
==> amazon-ebs.al2023: Using SSH communicator to connect: 18.222.63.67
==> amazon-ebs.al2023: Waiting for SSH to become available...
==> amazon-ebs.al2023: Connected to SSH!
==> amazon-ebs.al2023: Provisioning with shell script: /var/folders/rt/fqmt0tmx3fs1qfzbf3qxxq700000gn/T/packer-shell653292668
==> amazon-ebs.al2023: Waiting for process with pid 2085 to finish.
==> amazon-ebs.al2023: Amazon Linux 2023 Kernel Livepatch repository 154 kB/s | 16 kB 00:00
==> amazon-ebs.al2023: Package jq-1.7.1-49.amzn2023.0.2.aarch64 is already installed.
==> amazon-ebs.al2023: Dependencies resolved.
==> amazon-ebs.al2023: Nothing to do.
==> amazon-ebs.al2023: Complete!
==> amazon-ebs.al2023: 17 files removed
==> amazon-ebs.al2023: Stopping the source instance...
==> amazon-ebs.al2023: Stopping instance
==> amazon-ebs.al2023: Waiting for the instance to stop...
==> amazon-ebs.al2023: Creating AMI bastion-al2023-1754025080 from instance i-0b621ca091aa4c240
==> amazon-ebs.al2023: Attaching run tags to AMI...
==> amazon-ebs.al2023: AMI: ami-0b2b3b68aa3c5ada8
==> amazon-ebs.al2023: Waiting for AMI to become ready...
==> amazon-ebs.al2023: Skipping Enable AMI deprecation...
==> amazon-ebs.al2023: Skipping Enable AMI deregistration protection...
==> amazon-ebs.al2023: Modifying attributes on AMI (ami-0b2b3b68aa3c5ada8)...
==> amazon-ebs.al2023: Modifying: ami org arns
==> amazon-ebs.al2023: Modifying attributes on snapshot (snap-09ad35550e1438fb2)...
==> amazon-ebs.al2023: Adding tags to AMI (ami-0b2b3b68aa3c5ada8)...
==> amazon-ebs.al2023: Tagging snapshot: snap-09ad35550e1438fb2
==> amazon-ebs.al2023: Creating AMI tags
==> amazon-ebs.al2023: Adding tag: "Stage": "nonprod"
==> amazon-ebs.al2023: Adding tag: "ScanStatus": "pending"
==> amazon-ebs.al2023: Adding tag: "SourceAMI": "ami-0013ceeff668b979b"
==> amazon-ebs.al2023: Adding tag: "SourceAMIDescription": "Amazon Linux 2023 AMI 2023.7.20250527.1 arm64 HVM kernel-6.12"
==> amazon-ebs.al2023: Adding tag: "SourceAMIName": "al2023-ami-2023.7.20250527.1-kernel-6.12-arm64"
==> amazon-ebs.al2023: Adding tag: "SourceAMIOwnerAccountId": "137112412989"
==> amazon-ebs.al2023: Creating snapshot tags
==> amazon-ebs.al2023: Terminating the source AWS instance...
==> amazon-ebs.al2023: Cleaning up any extra volumes...
==> amazon-ebs.al2023: No volumes to clean up, skipping
==> amazon-ebs.al2023: Deleting temporary security group...
==> amazon-ebs.al2023: Deleting temporary keypair...
==> amazon-ebs.al2023: Running post-processor: (type manifest)
Build 'amazon-ebs.al2023' finished after 3 minutes 39 seconds.
==> Wait completed after 3 minutes 39 seconds
==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs.al2023: AMIs were created:
us-east-2: ami-0b2b3b68aa3c5ada8
--> amazon-ebs.al2023: AMIs were created:
us-east-2: ami-0b2b3b68aa3c5ada8
atmos packer output aws/bastion -s nonprod
# `atmos packer output` command is specific to Atmos (Packer itself does not have an `output` command)
# The command is used to get an output from a Packer manifest
# The manifest is generated by Packer when executing a `packer build` command
> atmos packer output aws/bastion -s nonprod
builds:
- artifact_id: us-east-2:ami-0c2ca16b7fcac7529
build_time: 1.753281956e+09
builder_type: amazon-ebs
custom_data: null
files: null
name: al2023
packer_run_uuid: 5114a723-92f6-060f-bae4-3ac2d0324557
- artifact_id: us-east-2:ami-0b2b3b68aa3c5ada8
build_time: 1.7540253e+09
builder_type: amazon-ebs
custom_data: null
files: null
name: al2023
packer_run_uuid: a57874d1-c478-63d7-cfde-9d91e513eb9a
last_run_uuid: a57874d1-c478-63d7-cfde-9d91e513eb9a
atmos packer output aws/bastion -s nonprod --query '.builds[0].artifact_id'
# `atmos packer output` command is specific to Atmos (Packer itself does not have an `output` command)
# The command is used to get an output from a Packer manifest
# The manifest is generated by Packer when executing a `packer build` command
# Use a YQ expression to get a specific section or attribute from the Packer manifest,
# in this case, the `artifact_id` from the first build.
> atmos packer output aws/bastion -s nonprod --query '.builds[0].artifact_id'
us-east-2:ami-0c2ca16b7fcac7529
atmos packer output aws/bastion -s nonprod -q '.builds[0].artifact_id | split(:)[1]'
# `atmos packer output` command is specific to Atmos (Packer itself does not have an `output` command).
# The command is used to get an output from a Packer manifest.
# The manifest is generated by Packer when executing a `packer build` command.
# Use a YQ expression to get a specific section or attribute from the Packer manifest,
# in this case, the AMI (second part after the `:`) from the `artifact_id` from the first build.
> atmos packer output aws/bastion -s nonprod -q '.builds[0].artifact_id | split(":")[1]'
ami-0c2ca16b7fcac7529