TerraformPilot

Terraform

Terraform with Packer - Build and Deploy Custom AMIs

Use Packer and Terraform together to build custom AMIs and deploy them. Golden image pipeline, HCP Packer integration, and automated image lifecycle.

LLuca Berton1 min read

Quick Answer

#

Build custom AMIs with Packer, then deploy them with Terraform:

# 1. Build AMI with Packer
packer build template.pkr.hcl
 
# 2. Deploy with Terraform (uses latest AMI)
terraform apply

Packer Template

#
# image.pkr.hcl
packer {
  required_plugins {
    amazon = {
      version = ">= 1.2.0"
      source  = "github.com/hashicorp/amazon"
    }
  }
}
 
source "amazon-ebs" "ubuntu" {
  ami_name      = "myapp-{{timestamp}}"
  instance_type = "t3.micro"
  region        = "us-east-1"
 
  source_ami_filter {
    filters = {
      name                = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
      virtualization-type = "hvm"
      root-device-type    = "ebs"
    }
    owners      = ["099720109477"]
    most_recent = true
  }
 
  ssh_username = "ubuntu"
 
  tags = {
    Name        = "myapp"
    Environment = "production"
    Builder     = "packer"
  }
}
 
build {
  sources = ["source.amazon-ebs.ubuntu"]
 
  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx nodejs npm",
      "sudo systemctl enable nginx",
    ]
  }
 
  provisioner "file" {
    source      = "app/"
    destination = "/tmp/app"
  }
 
  provisioner "shell" {
    inline = [
      "sudo mv /tmp/app /opt/app",
      "cd /opt/app && sudo npm install --production",
    ]
  }
}

Terraform Deployment

#
# Look up the latest Packer-built AMI
data "aws_ami" "app" {
  most_recent = true
  owners      = ["self"]
 
  filter {
    name   = "name"
    values = ["myapp-*"]
  }
 
  filter {
    name   = "tag:Builder"
    values = ["packer"]
  }
}
 
resource "aws_launch_template" "app" {
  name_prefix   = "myapp-"
  image_id      = data.aws_ami.app.id
  instance_type = var.instance_type
 
  network_interfaces {
    security_groups = [aws_security_group.app.id]
  }
 
  tag_specifications {
    resource_type = "instance"
    tags = {
      Name    = "myapp"
      AMI     = data.aws_ami.app.id
      AMIName = data.aws_ami.app.name
    }
  }
}
 
resource "aws_autoscaling_group" "app" {
  desired_capacity = 2
  max_size         = 4
  min_size         = 1
  vpc_zone_identifier = aws_subnet.private[*].id
 
  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
 
  instance_refresh {
    strategy = "Rolling"
    preferences {
      min_healthy_percentage = 50
    }
  }
}

CI/CD Pipeline

#
# GitHub Actions
jobs:
  build-ami:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-packer@main
      - run: packer init image.pkr.hcl
      - run: packer build image.pkr.hcl
 
  deploy:
    needs: build-ami
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
      - run: terraform apply -auto-approve
        # data.aws_ami.app automatically picks up the new AMI

Golden Image Workflow

#
Source AMI (Ubuntu) → Packer Build → Custom AMI → Terraform Deploy
     │                    │               │              │
     └─ Base OS      Install deps    Tagged/versioned  ASG rolling
                     Config app      Scanned           update
                     Harden OS       Tested
#

Conclusion

#

Packer builds immutable machine images; Terraform deploys them. Use aws_ami data source with owners = ["self"] to auto-discover the latest Packer-built AMI. Combine with ASG instance refresh for zero-downtime rolling deployments. Tag AMIs with Builder = "packer" for easy filtering.

#Terraform#Packer#AWS#DevOps#Infrastructure as Code

Share this article