Skip to content

HashiCorp Packer

Chapter 42: Packer - Machine Image Building

Section titled “Chapter 42: Packer - Machine Image Building”

Automated Machine Image Creation with Packer

Section titled “Automated Machine Image Creation with Packer”

Packer is an open-source tool by HashiCorp for creating identical machine images for multiple platforms from a single source configuration.

Packer Overview
+------------------------------------------------------------------+
| |
| +------------------------+ |
| | Packer | |
| +------------------------+ |
| | |
| +---------------------+---------------------+ |
| | | | | |
| v v v v |
| +----------+ +----------+ +----------+ +----------+ |
| | Builders | | Provisio | | Post- | | Templates| |
| | | | -ners | | Processors| | | |
| | - AWS | | - Shell | | - Upload | | - JSON | |
| | - Azure | | - Ansible| | - Scan | | - HCL2 | |
| | - GCP | | - Puppet | | - Tag | | - Var | |
| | - Docker | | - Chef | | - Test | | - Packer| |
| +----------+ +----------+ +----------+ +----------+ |
| |
+------------------------------------------------------------------+
FeatureDescription
BuildersCreate images for different platforms
ProvisionersConfigure the image
Post-ProcessorsProcess images after creation
TemplatesDefine image configuration

Packer Workflow
+------------------------------------------------------------------+
| |
| +----------+ +----------+ +----------+ +----------+ |
| | Template | -> | Builder | -> | Provision| -> | Post- | |
| | (JSON/ | | (Create | | -er | | Processor| |
| | HCL2) | | Instance| | (Config) | | (Export) | |
| +----------+ +----------+ +----------+ +----------+ |
| | | | | |
| v v v v |
| +----------------------------------------------------------+ |
| | State Management | |
| +----------------------------------------------------------+ |
| |
| Output: Machine Image (AMI, VHD, Docker Image, etc.) |
| |
+------------------------------------------------------------------+
AWS Builders
+------------------------------------------------------------------+
| |
| +------------------+ +------------------+ +------------------+ |
| | AMI Builder | | EC2 Image Builder| | EBS Builder | |
| | | | | | | |
| | - Source AMI | | - Pipeline | | - Volume-based | |
| | - Instance Store | | - Components | | - Snapshot | |
| | - Launch | | - Distribution | | - Registration | |
| +------------------+ +------------------+ +------------------+ |
| |
| +------------------+ +------------------+ |
| | Instance Builder | | Chroot Builder | |
| | | | | |
| | - EC2 Instance | | - No running | |
| | - SSH/WinRM | | instance | |
| | - Full support | | - Fast build | |
| +------------------+ +------------------+ |
| |
+------------------------------------------------------------------+

packer.pkr.hcl
# Required plugins
packer {
required_plugins {
amazon = {
version = ">= 1.0.0"
source = "github.com/hashicorp/amazon"
}
}
}
# Variables
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "ami_name" {
type = string
default = "web-server-{{timestamp}}"
}
# Data sources
data "amazon-ami" "ubuntu" {
filters = {
name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"]
}
# Source definition
source "amazon-ebs" "ubuntu" {
region = var.aws_region
source_ami = data.amazon-ami.ubuntu.id
instance_type = "t3.medium"
ssh_username = "ubuntu"
ami_name = var.ami_name
ami_description = "Web server AMI built with Packer"
tags = {
Name = "web-server"
BuiltBy = "Packer"
}
}
# Build definition
build {
name = "web-server"
sources = [
"source.amazon-ebs.ubuntu"
]
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl enable nginx"
]
}
provisioner "file" {
source = "./configs/nginx.conf"
destination = "/tmp/nginx.conf"
}
provisioner "shell" {
inline = [
"sudo cp /tmp/nginx.conf /etc/nginx/nginx.conf",
"sudo systemctl restart nginx"
]
}
post-processor "manifest" {
output = "manifest.json"
}
}
{
"variables": {
"aws_region": "us-east-1",
"ami_name": "web-server-{{timestamp}}"
},
"builders": [
{
"type": "amazon-ebs",
"region": "{{user `aws_region`}}",
"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"]
},
"instance_type": "t3.medium",
"ssh_username": "ubuntu",
"ami_name": "{{user `ami_name`}}",
"tags": {
"Name": "web-server",
"BuiltBy": "Packer"
}
}
],
"provisioners": [
{
"type": "shell",
"inline": [
"sudo apt-get update",
"sudo apt-get install -y nginx"
]
}
],
"post-processors": [
{
"type": "manifest",
"output": "manifest.json"
}
]
}

Packer Provisioners
+------------------------------------------------------------------+
| |
| +------------------+ +------------------+ +------------------+ |
| | Shell | | Ansible | | Chef | |
| | | | | | | |
| | - inline | | - playbook | | - cookbook | |
| | - script | | - galaxy | | - role | |
| | - execute_command| | - inventory | | - attributes | |
| +------------------+ +------------------+ +------------------+ |
| |
| +------------------+ +------------------+ +------------------+ |
| | Puppet | | File | | PowerShell | |
| | | | | | | |
| | - manifest | | - upload files | | - scripts | |
| | - module | | - directories | | - inline | |
| | - facter | | - permissions | | - elevated | |
| +------------------+ +------------------+ +------------------+ |
| |
+------------------------------------------------------------------+
# Inline commands
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx curl wget",
"sudo systemctl enable nginx"
]
}
# Script file
provisioner "shell" {
script = "./scripts/setup.sh"
}
# Script with arguments
provisioner "shell" {
script = "./scripts/configure.sh"
execute_command = "sudo -S -E bash -s '{{ .Path }}'"
environment_vars = [
"APP_ENV=production",
"DB_HOST=${var.db_host}"
]
}
# Expect disconnect (for reboot)
provisioner "shell" {
inline = ["sudo reboot"]
expect_disconnect = true
}
# Pause before next provisioner
provisioner "shell" {
inline = ["echo 'Waiting for reboot...'"]
pause_before = "30s"
start_retry_timeout = "10m"
}
# Local Ansible
provisioner "ansible" {
playbook_file = "./ansible/site.yml"
extra_arguments = [
"--extra-vars", "app_env=production"
]
}
# Ansible Galaxy
provisioner "ansible" {
playbook_file = "./ansible/site.yml"
galaxy_file = "./ansible/requirements.yml"
galaxy_command = "ansible-galaxy install -r {{.GalaxyFile}}"
}
# Remote Ansible (Ansible installed on instance)
provisioner "ansible-remote" {
playbook_file = "./ansible/site.yml"
ansible_ssh_user = "ubuntu"
}
# Upload single file
provisioner "file" {
source = "./configs/app.conf"
destination = "/tmp/app.conf"
}
# Upload directory
provisioner "file" {
source = "./configs/"
destination = "/tmp/configs/"
}
# Upload with direction (for Windows)
provisioner "file" {
source = "./configs/app.conf"
destination = "C:/Temp/app.conf"
direction = "upload"
}

source "amazon-ebs" "web-server" {
# Region and source AMI
region = "us-east-1"
source_ami = "ami-0abcdef1234567890"
# Instance configuration
instance_type = "t3.medium"
spot_instance_types = ["t3.medium", "t3.large"]
spot_price = "0.05"
# Network configuration
vpc_id = "vpc-12345678"
subnet_id = "subnet-12345678"
security_group_ids = ["sg-12345678"]
# SSH configuration
ssh_username = "ubuntu"
ssh_keypair_name = "my-keypair"
ssh_private_key_file = "./keys/my-keypair.pem"
# AMI configuration
ami_name = "web-server-{{timestamp}}"
ami_description = "Web server AMI"
ami_users = ["123456789012"] # Share with accounts
ami_groups = ["all"] # Make public (or specific groups)
# AMI tags
tags = {
Name = "web-server"
Environment = "production"
BuiltBy = "Packer"
Project = "web-app"
}
# Snapshot tags
snapshot_tags = {
Name = "web-server-snapshot"
}
# Launch template
launch_template = {
id = "lt-1234567890abcdef"
version = "$Latest"
}
}
# Windows AMI Builder
source "amazon-ebs" "windows" {
region = "us-east-1"
source_ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
# Windows-specific
user_data_file = "./scripts/windows-userdata.ps1"
communicator = "winrm"
winrm_username = "Administrator"
winrm_password = "PackerPassword123!"
winrm_use_ssl = true
winrm_insecure = true
# AMI configuration
ami_name = "windows-server-{{timestamp}}"
}
source "amazon-ebs" "spot-instance" {
region = "us-east-1"
source_ami = "ami-0abcdef1234567890"
# Use spot instances
spot_price = "auto"
spot_instance_types = [
"t3.medium",
"t3.large",
"m5.medium"
]
# Fallback to on-demand if spot unavailable
spot_price_auto_product = "Linux/Unix (Amazon VPC)"
instance_type = "t3.medium" # Fallback
ssh_username = "ubuntu"
ami_name = "spot-built-ami-{{timestamp}}"
}

Post-Processors
+------------------------------------------------------------------+
| |
| +------------------+ +------------------+ +------------------+ |
| | Manifest | | Amazon Import | | Vagrant | |
| | | | | | | |
| | - Output JSON | | - Import to EC2 | | - Vagrant box | |
| | - Image IDs | | - VM Import | | - VirtualBox | |
| | - Metadata | | - Conversion | | - AWS provider | |
| +------------------+ +------------------+ +------------------+ |
| |
| +------------------+ +------------------+ +------------------+ |
| | Docker | | Artifactory | | Checksum | |
| | | | | | | |
| | - Docker image | | - Upload | | - SHA256 | |
| | - Push to repo | | - Repository | | - MD5 | |
| | - Tag | | - Authentication | | - Verification | |
| +------------------+ +------------------+ +------------------+ |
| |
+------------------------------------------------------------------+
build {
post-processor "manifest" {
output = "manifest.json"
strip_path = true
custom_data = {
git_commit = "${var.git_commit}"
build_number = "${var.build_number}"
}
}
}
# Build Docker image from AMI
build {
sources = ["source.amazon-ebs.ubuntu"]
post-processor "docker-import" {
repository = "my-app"
tag = "latest"
}
post-processor "docker-push" {
login = true
login_username = var.docker_username
login_password = var.docker_password
login_server = "docker.io"
}
}

source "amazon-ebs" "base" {
region = "us-east-1"
source_ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
ssh_username = "ubuntu"
ami_name = "base-ami-{{timestamp}}"
# Copy to multiple regions
ami_regions = [
"us-west-1",
"us-west-2",
"eu-west-1",
"eu-central-1",
"ap-southeast-1"
]
# Region-specific AMI names
ami_region_kms_key_ids = {
us-west-1 = "alias/aws/ebs"
us-west-2 = "alias/aws/ebs"
eu-west-1 = "alias/aws/ebs"
eu-central-1 = "alias/aws/ebs"
}
}
# Build in multiple regions simultaneously
variable "regions" {
type = list(string)
default = ["us-east-1", "eu-west-1", "ap-southeast-1"]
}
build {
name = "multi-region"
dynamic "sources" {
for_each = var.regions
iterator = region
content {
source = "source.amazon-ebs.base"
# Override region-specific settings
region = region.value
ami_name = "app-${region.value}-{{timestamp}}"
}
}
provisioner "shell" {
inline = ["sudo apt-get update && sudo apt-get install -y nginx"]
}
}

Golden Image Pipeline
+------------------------------------------------------------------+
| |
| Stage 1: Base Image |
| +----------------------------------------------------------+ |
| | - OS updates and patches | |
| | - Common packages | |
| | - Security hardening | |
| | - Base configuration | |
| +----------------------------------------------------------+ |
| | |
| v |
| Stage 2: Application Image |
| +----------------------------------------------------------+ |
| | - Application runtime | |
| | - Application dependencies | |
| | - Application configuration | |
| | - Application-specific hardening | |
| +----------------------------------------------------------+ |
| | |
| v |
| Stage 3: Deployment Image |
| +----------------------------------------------------------+ |
| | - Environment-specific config | |
| | - Secrets and credentials | |
| | - Monitoring agents | |
| | - Final validation | |
| +----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
# Base image template
source "amazon-ebs" "base" {
region = "us-east-1"
source_ami = data.amazon-ami.ubuntu.id
instance_type = "t3.medium"
ssh_username = "ubuntu"
ami_name = "base-image-{{timestamp}}"
ami_users = var.account_ids
}
build {
name = "base-image"
sources = ["source.amazon-ebs.base"]
# System updates
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get upgrade -y",
"sudo apt-get install -y curl wget jq unzip"
]
}
# Security hardening
provisioner "shell" {
script = "./scripts/harden.sh"
}
# Install common tools
provisioner "shell" {
script = "./scripts/install-tools.sh"
}
# Output manifest
post-processor "manifest" {
output = "base-manifest.json"
}
}
# Application image (uses base image)
variable "base_ami" {
type = string
}
source "amazon-ebs" "app" {
region = "us-east-1"
source_ami = var.base_ami
instance_type = "t3.medium"
ssh_username = "ubuntu"
ami_name = "app-image-{{timestamp}}"
}
build {
name = "app-image"
sources = ["source.amazon-ebs.app"]
# Install application
provisioner "ansible" {
playbook_file = "./ansible/app.yml"
}
# Configure application
provisioner "file" {
source = "./configs/"
destination = "/opt/app/configs/"
}
post-processor "manifest" {
output = "app-manifest.json"
}
}

.github/workflows/packer.yml
name: Build AMI with Packer
on:
push:
branches: [main]
paths:
- 'packer/**'
pull_request:
branches: [main]
env:
AWS_REGION: us-east-1
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Packer
uses: hashicorp/setup-packer@main
with:
version: "1.9.0"
- name: Validate Template
run: |
cd packer
packer init .
packer validate .
build:
needs: validate
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Setup Packer
uses: hashicorp/setup-packer@main
with:
version: "1.9.0"
- name: Build AMI
run: |
cd packer
packer init .
packer build -machine-readable .
env:
PKR_VAR_ami_name: "app-${{ github.sha }}"
- name: Upload manifest
uses: actions/upload-artifact@v3
with:
name: packer-manifest
path: packer/manifest.json
// Jenkinsfile
pipeline {
agent any
environment {
AWS_REGION = 'us-east-1'
PACKER_VERSION = '1.9.0'
}
stages {
stage('Validate') {
steps {
sh '''
packer init packer/
packer validate packer/
'''
}
}
stage('Build AMI') {
steps {
withAWS(credentials: 'aws-credentials', region: "${AWS_REGION}") {
sh '''
cd packer
packer build \
-var "ami_name=app-${BUILD_NUMBER}" \
-var "git_commit=${GIT_COMMIT}" \
.
'''
}
}
post {
success {
archiveArtifacts artifacts: 'packer/manifest.json', fingerprint: true
}
}
}
stage('Update Terraform') {
steps {
script {
def manifest = readJSON file: 'packer/manifest.json'
def amiId = manifest.builds[0].artifact_id
sh """
cd terraform
terraform apply -auto-approve \
-var "ami_id=${amiId}"
"""
}
}
}
}
post {
always {
cleanWs()
}
}
}

Packer Security Best Practices
+------------------------------------------------------------------+
| |
| 1. Credential Management |
| +--------------------------------------------------------+ |
| | - Use IAM roles for EC2 instances | |
| | - Never hardcode credentials | |
| | - Use temporary credentials (STS) | |
| | - Rotate keys regularly | |
| +--------------------------------------------------------+ |
| |
| 2. Image Security |
| +--------------------------------------------------------+ |
| | - Start from trusted source AMIs | |
| | - Apply latest security patches | |
| | - Remove sensitive data before AMI creation | |
| | - Use encrypted AMIs | |
| +--------------------------------------------------------+ |
| |
| 3. Network Security |
| +--------------------------------------------------------+ |
| | | Use isolated VPC for building | |
| | | Restrict security group rules | |
| | | Use VPC endpoints for AWS services | |
| +--------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
# Use spot instances for cost savings
source "amazon-ebs" "optimized" {
# ... other config ...
# Spot instances
spot_price = "auto"
spot_instance_types = ["t3.medium", "t3.large", "m5.medium"]
# Faster EBS volumes
volume_type = "gp3"
volume_size = 20
encrypted = true
# Parallel provisioning
skip_create_ami = false
}
# Parallel builds
build {
sources = [
"source.amazon-ebs.region1",
"source.amazon-ebs.region2",
"source.amazon-ebs.region3"
]
# Run provisioners in parallel
provisioner "shell" {
inline = ["echo 'Parallel provisioning'"]
}
}
packer/
+------------------------------------------------------------------+
| |
| packer.pkr.hcl # Main template |
| variables.pkr.hcl # Variable definitions |
| sources.pkr.hcl # Source definitions |
| builds.pkr.hcl # Build definitions |
| |
| scripts/ # Shell scripts |
| +-- setup.sh |
| +-- harden.sh |
| +-- install-tools.sh |
| |
| configs/ # Configuration files |
| +-- nginx.conf |
| +-- app.conf |
| |
| ansible/ # Ansible playbooks |
| +-- site.yml |
| +-- roles/ |
| +-- web-server/ |
| |
| manifests/ # Output manifests |
| +-- base-manifest.json |
| +-- app-manifest.json |
| |
+------------------------------------------------------------------+

Packer is essential for creating consistent, immutable infrastructure. SREs use it to bake configuration into AMIs, reducing configuration drift and deployment time.

Packer in DevOps/SRE
+------------------------------------------------------------------+
| |
| SRE Image Management: |
| |
| 1. Immutable Infrastructure |
| +----------------------------------------------------------+ |
| | - Bake configuration into AMIs | |
| | - Same image across dev/staging/production | |
| | - Faster deployments, consistent environments | |
| +----------------------------------------------------------+ |
| |
| 2. Security & Compliance |
| +----------------------------------------------------------+ |
| | - Security hardening in the image | |
| | - Automated patching during image build | |
| | - Audit trail of image versions | |
| +----------------------------------------------------------+ |
| |
| 3. Golden Image Pipeline |
| +----------------------------------------------------------+ |
| | - Automated daily/weekly image builds | |
| | - Integration with CI/CD pipelines | |
| | - Automated testing before image promotion | |
| +----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

Terminal window
# Install Packer on Arch Linux
sudo pacman -S packer
# Verify installation
packer --version
# Build image
packer build template.pkr.hcl
# Validate template
packer validate template.pkr.hcl
# Debug build
PACKER_LOG=1 packer build template.pkr.hcl

Packer Anti-Patterns
+------------------------------------------------------------------+
| |
| ❌ Mistake 1: Building Images Manually |
| +----------------------------------------------------------+ |
| | Problem: Inconsistent images, configuration drift | |
| | Impact: Snowflake servers, deployment failures | |
| | Fix: Automate image building in CI/CD pipeline | |
| +----------------------------------------------------------+ |
| |
| ❌ Mistake 2: Not Using Version Control for Templates |
| +----------------------------------------------------------+ |
| | Problem: No audit trail, hard to reproduce images | |
| | Impact: Can't rollback, compliance gaps | |
| | Fix: Store templates in Git, use tags for versions | |
| +----------------------------------------------------------+ |
| |
| ❌ Mistake 3: Not Testing Built Images |
| +----------------------------------------------------------+ |
| | Problem: Bugs discovered after deployment | |
| | Impact: Failed deployments, rollbacks | |
| | Fix: Use test frameworks (Terratest, Inspec) | |
| +----------------------------------------------------------+ |
| |
| ❌ Mistake 4: Not Using Parallel Builds |
| +----------------------------------------------------------+ |
| | Problem: Slow image building for multiple regions | |
| | Impact: Long CI/CD pipeline times | |
| | Fix: Use parallel builds for multi-region AMIs | |
| +----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

  1. Q: Explain the difference between Packer and configuration management tools like Ansible.

    • A: Packer creates machine images (AMIs) at build time - it’s for provisioning. Ansible configures running systems at deployment time. Use both: Packer bakes base configuration into AMI, Ansible handles deployment-time configuration.
  2. Q: Why use Golden Images?

    • A: Golden images reduce deployment time, ensure consistency, enable faster scaling, reduce configuration drift, and improve security by baking in hardening. They also simplify troubleshooting by having known good states.
  1. Q: Design an automated AMI pipeline.
    • A: Use Packer with: (1) GitLab CI or CodePipeline trigger on schedule or code change, (2) Packer builds AMI with latest patches and configuration, (3) Terratest validates image functionality, (4) Resulting AMI tagged and stored, (5) Auto Scaling Groups updated to use new AMI.

Packer Troubleshooting
+------------------------------------------------------------------+
| |
| Issue: SSH Connection Timeout |
| +--------------------------------------------------------+ |
| | Solutions: | |
| | - Check security group allows SSH (port 22) | |
| | - Verify ssh_username matches AMI | |
| | - Increase ssh_timeout value | |
| | - Check subnet route table | |
| +--------------------------------------------------------+ |
| |
| Issue: AMI Creation Failed |
| +--------------------------------------------------------+ |
| | Solutions: | |
| | - Check IAM permissions | |
| | - Verify AMI name is unique | |
| | - Check EBS volume limits | |
| | - Review instance logs | |
| +--------------------------------------------------------+ |
| |
| Issue: Provisioner Failures |
| +--------------------------------------------------------+ |
| | Solutions: | |
| | - Enable debug logging: PACKER_LOG=1 | |
| | - Check script exit codes | |
| | - Verify file paths exist | |
| | - Test provisioners manually | |
| +--------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
Terminal window
# Enable debug logging
PACKER_LOG=1 packer build template.pkr.hcl
# Save log to file
PACKER_LOG=1 PACKER_LOG_PATH=packer.log packer build template.pkr.hcl
# Debug mode (keeps instance running on failure)
packer build -debug template.pkr.hcl
# On-error behavior
packer build -on-error=ask template.pkr.hcl
# Options: cleanup, abort, ask, run-cleanup-provisioner

TopicKey Points
BuildersUse appropriate builder for use case (EBS, instance, chroot)
ProvisionersShell for simple, Ansible for complex configurations
SecurityUse IAM roles, encrypt AMIs, isolate build environment
CI/CDIntegrate with pipelines for automated image building
Multi-RegionUse ami_regions for distribution, parallel builds for speed
Golden ImagesBuild layered images for maintainability


Chapter 42 Summary
+------------------------------------------------------------------+
| |
| Packer - Machine Image Building |
| +------------------------------------------------------------+ |
| | - Immutable infrastructure through AMI baking | |
| | - Automated image creation with builders | |
| | - Configuration provisioning with provisioners | |
| | - Security best practices (IAM, encryption, isolation) | |
| +------------------------------------------------------------+ |
| |
| Key Components |
| +------------------------------------------------------------+ |
| | - Builders: EBS, Instance Store, Chroot | |
| | - Provisioners: Shell, Ansible, PowerShell | |
| | - Post-processors: AMIManage, Vagrant | |
| | - Variables: User, local, environment | |
| +------------------------------------------------------------+ |
| |
| Best Practices |
| +------------------------------------------------------------+ |
| | - Use IAM roles, not access keys | |
| | - Encrypt AMIs with KMS | |
| | - Build in isolated VPCs | |
| | - Integrate with CI/CD pipelines | |
| +------------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

Next Chapter: Chapter 43 - Ansible on AWS