← Back to list

From Zero to Live in Minutes: Automate Your Docker App Deployment on AWS EC2 with Packer, Terraform…

Turn tedious AWS provisioning into a one-command magic trick using Packer, Terraform, S3 and Docker.

Mukesh Vast in DevOps.dev · 2025-06-05 12:39 · 5 claps · 7.0 min read
#infrastructure-as-code #terraform #devops #packers #amazon-machine-images
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud 🥊 · Combat Sports

From Zero to Live in Minutes: Automate Your Docker App Deployment on AWS EC2 with Packer, Terraform and S3

Turn tedious AWS provisioning into a one-command magic trick using Packer, Terraform, S3 and Docker.

If you don’t have medium account, read full article free ***here***. (no login needed)

Imagine deploying your Dockerized application to AWS EC2 with a single command — no manual setups, no repetitive tasks.

In this article, we’ll explore a powerful and reproducible way to automate EC2 instance provisioning using tools like Packer (Automates the creation of machine images), Terraform (Manages infrastructure as code), Docker (Containers for application deployment) and Amazon S3, while comparing the two main automation techniques: Packer-based AMI baking and User Data scripts.

✨ Why Automate EC2 Instance Creation?

Manual EC2 workflows:

  • Launch EC2 from console:
  • SSH into the instance
  • Install Docker, Docker Compose, dependencies
  • Clone code, copy Compose files.
  • Launch containers manually

Problems:

  • Time-consuming
  • Not repeatable
  • Prone to misconfiguration

By automating you get:

  • Efficiency: Rapid deployment reduces time-to-market.
  • Scalability: Easily replicate environments.
  • Cost-Effectiveness: Ephemeral instances save resources.
  • Immutable infrastructure
  • Version-controlled images

Real-World Use Cases where this setup shines:

  • 🔁 CI/CD Pipelines: Automatically spin up clean, isolated environments to run integration tests using Docker Compose apps.
  • 🧪 Demo/POC Environments: Create demo-ready stacks for product showcases or client trials.
  • 👩‍💻 Per-Developer Environments: Give each developer their own disposable sandboxed EC2 environment.
  • 📦 Microservice Previews: Test new microservice versions in a consistent isolated setup without polluting shared infra.
  • 🏫 Workshops or Bootcamps: Provision N identical instances using the same AMI to simplify technical training or cloud labs.
  • 💸 Cost-Optimized Dev Servers: Use Terraform’s destroy lifecycle to spin down environments outside business hours.

Here’s a walkthrough of a simple HelloWorld Nginx app as an example.

While the example is intentionally simple, it serves as a launchpad for mastering more advanced concepts. By containerizing even a basic app and deploying it automatically with Packer and Terraform, you gain hands-on understanding of key DevOps practices:

  • 🔄 Immutable Infrastructure: The base image doesn’t change between launches, eliminating config drift.
  • 🚀 Infrastructure as Code (IaC): You treat environment creation like software builds — repeatable, tested, and version-controlled.
  • 🔐 IAM and Permissions: The setup introduces using roles for granting least-privilege S3 access to instances.
  • ⚙️ User Data vs Prebaked AMIs: You experience the pros/cons of dynamic vs static environment bootstrapping.
  • 📈 CI/CD Simulation: Replace Nginx with your app and integrate into pipelines for automated testing or ephemeral previews.

🧪 Step-by-Step Setup Guide

✅ Confirm Prerequisites Checklist

  • You have AWS access and aws configure is done
  • You have an SSH Key Pair in EC2
  • Your security group allows port 80 (HTTP)
  • You created an S3 bucket
  • You have installed: AWS CLI, Terraform, Packer. Check install instructions here.

📁 Expanded Project File Structure with Contents

This “Hello World” is just the seed — how far you take it is up to your use case! This is based on Amazon Linux 2 with a t3.micro instance.

hello-aws-ephemeral-app/
├── packer/
│   └── docker-amazon.pkr.hcl         # Packer HCL file to build AMI with Docker
├── terraform/
│   ├── main.tf                       # Terraform EC2 provisioning logic
│   ├── iam.tf                        # IAM Roles/Policies used in main.tf
│   ├── variables.tf                  # Variables used in main.tf
│   ├── outputs.tf                    # Output EC2 public IP
│   └── terraform.tfvars              # User-supplied values
├── docker-compose/
│   └── docker-compose.yml            # Simple Nginx Hello World app
├── scripts/
│   ├── launch_instance.sh            # Runs terraform to launch instance
│   └── destroy_instance.sh           # Destroys provisioned resources

Here is the link to the full code — https://github.com/mukizone/hello-aws-ephemeral-app

🔧 Step 1: Prepare a Base AMI (1-time setup using Packer)

packer/docker-amazon.pkr.hcl

packer {
  required_plugins {
    amazon = {
      version = ">= 1.0.0"
      source  = "github.com/hashicorp/amazon"
    }
  }
}
source "amazon-ebs" "amazon-linux-docker" {
  region           = "ap-south-1"
  source_ami_filter {
    filters = {
      name                = "al2023-ami-2023*"
      virtualization-type = "hvm"
      architecture         = "x86_64"
      root-device-type    = "ebs"
    }
    owners      = ["137112412989"]  # ← Amazon official AMI owner
    most_recent = true
  }
  instance_type    = "t3.micro"
  ssh_username     = "ec2-user"
  ami_name         = "amazon-linux-docker-t3.micro-ami"
}
build {
  sources = ["source.amazon-ebs.amazon-linux-docker"]
  provisioner "shell" {
    expect_disconnect = true
    valid_exit_codes  = [0]
    inline = [
      "sudo dnf update -y",
      "sudo dnf install -y docker",
      "sudo systemctl enable docker",
      "sudo systemctl start docker",
      "sudo usermod -aG docker ec2-user",

      # Install AWS CLI v2
      "curl \"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip\" -o \"awscliv2.zip\"",
      "unzip awscliv2.zip",
      "sudo ./aws/install",

      # Install Docker Compose plugin to system-wide path
      "sudo mkdir -p /usr/libexec/docker/cli-plugins",
      "sudo curl -SL https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-x86_64 -o /usr/libexec/docker/cli-plugins/docker-compose",
      "sudo chmod +x /usr/libexec/docker/cli-plugins/docker-compose"
    ]
  }
}

This shell provisioner runs a list of Linux commands inside the EC2 instance during the Packer build. It’s like pre-installing everything into the AMI. This script ensures the EC2 image you’re building with Packer:

  • Has Docker installed and running
  • Has AWS CLI installed to fetch files from S3
  • Supports Docker Compose v2
  • Lets the default user run Docker commands without sudo

Packer command:

cd hello-aws-ephemeral-app/packer
packer init .
packer build .\docker-amazon.pkr.hcl

Packer output:

Verify on AWS console that the AMI image ‘amazon-linux-docker-t3.micro-ami’ is created: e.g ami-01c08610922e8555e

🔁 Note the output AMI ID and replace in terraform.tfvars.

🏗️ Step 2: Terraform Script (Launch EC2 Instances and containers in it using Docker Compose)

docker-compose/docker-compose.yml

version: '3.8'
services:
  hello:
    image: nginx:latest
    ports:
      - "80:80"

This launches the official Nginx Docker image exposing port 80. When you hit the instance IP, you’ll see the default “Welcome to Nginx” page.

terraform/main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
...
...

resource "aws_instance" "docker_app" {
  ami           = var.ami_id
  instance_type = var.instance_type
  key_name      = var.key_name
  subnet_id     = var.subnet_id
  vpc_security_group_ids = [aws_security_group.docker_sg.id]
  iam_instance_profile   = aws_iam_instance_profile.ec2_s3_instance_profile.name

  root_block_device {
    volume_size = var.volume_size      # in GB
    volume_type = "gp3"                # or "gp2", "io1" etc.
    delete_on_termination = true
  }

  user_data = <<-EOF
              #!/bin/bash
              cd /home/ec2-user
              aws s3 cp s3://${var.s3_bucket}/hello-compose.prod.yaml /home/ec2-user/docker-compose.yml
              docker compose -f docker-compose.yml up -d
              EOF

}

Using the Script in EC2 user_data (Dynamic at Launch)

✅ What It Means: You pass the script in the user_data field during EC2 creation via Terraform, CLI, or console.

🧠 How It Works: AWS Cloud-Init runs this script only once at instance launch. The script can:

Pull files from S3 Install software Start services

Check code in **terraform/main.tf**

🛡️ IAM Permissions Reminder

Make sure the EC2 instance has S3 read permissions, either via:

  • Attached IAM Role with policy like:
{
  "Effect": "Allow",
  "Action": ["s3:GetObject"],
  "Resource": "arn:aws:s3:::your-s3-bucket/*"
}
  • Define the IAM Role & Policy in Terraform

Check code in **terraform/iam.tf**

Update the following in terraform/terraform.tfvars:

  • ami_id (from packer output)
  • aws_region , vpc_id,subnet_id, key_name, s3_bucket

terraform/terraform.tfvars

aws_region        = "aws-region"        # <- your aws region e.g ap-south-1
ami_id            = "ami-0a4f9fdadc49150df" # <— your AMI from packer
instance_type     = "aws-instance-type" # <— choose your instance type, t3.micro
key_name          = "your-key-name"     # <— must exist in AWS EC2 > Key Pairs
vpc_id            = "vpc-xxxxxxxx"      # <- get from your VPC
subnet_id         = "subnet-xxxxxxxx"   # <— get from your VPC Subnets
volume_size       = "xx"                # <- size of EBS Volume eg 4,8..    
s3_bucket         = "your-aws-bucket"   # <- S3 bucket having COmpose file

run scripts/launch_instance.sh

Terraform will:

  • Launch EC2 instance with your AMI
  • Create IAM role + S3 policy
  • Attach policy to role
  • Create instance profile
  • Launch EC2 with IAM profile attached
  • Your EC2 instance can now run aws s3 cp commands!
  • Inject user-data that pulls Compose from S3
  • Start your container (e.g. NGINX)

It takes ~1–2 minutes.

When you hit the instance IP, you’ll see the default “Welcome to Nginx” page.

Check the instance and “docker ps” to see running containers:

Ready to streamline your AWS deployments? Implement this approach and share your experience!

Thank you for reading. Follow me on Medium & LinkedIn

🧱 Appendix

Appendix 1 : Prerequisites

[embed]

Appendix 2 : Delete or Destroy the instances using terraform script

Run the script in scripts/destroy_instance.sh

Appendix 3 : To check if your user_data ran and debug it:

SSH into your instance. (ssh -i your-key.pem ec2-user@<EC2-PUBLIC-IP>)

Then run:

📄 1. Main cloud-init log

cat /var/log/cloud-init-output.log

This shows you both stdout and stderr of your user_data script. Look here to confirm if:

  • Docker commands ran
  • S3 file copy failed
  • Any other errors occurred

📄 2. System log (from AWS Console)

Alternatively, from the AWS Console:

  1. Go to EC2 → Instances
  2. Select your instance
  3. Click Actions > Monitor and troubleshoot > Get system log

This shows boot-level output and early cloud-init script results.

Appendix 4 : Docker Restart Ability

Add These Enhancements to Your Packer Template

1. Enable Docker at Boot (already present)

This line: Ensures Docker service starts on every reboot ✅

sudo systemctl enable docker

2. Add Docker Compose restart policy in your docker-compose.yml

This is critical to restart containers, not just the Docker daemon. Add the *restart: always* block to each service:

version: '3.8'services:
  hello:
    image: nginx
    ports:
      - "80:80"
    restart: always  # ← This is key

This makes your containers auto-restart after EC2 reboot.


메타데이터
post_id
1a67294882cd
slug
from-zero-to-live-in-minutes-automate-your-docker-app-deployment-on-aws-ec2-with-packer-1a67294882cd
url
https://blog.devops.dev/from-zero-to-live-in-minutes-automate-your-docker-app-deployment-on-aws-ec2-with-packer-1a67294882cd
canonical_url
https://blog.devops.dev/from-zero-to-live-in-minutes-automate-your-docker-app-deployment-on-aws-ec2-with-packer-1a67294882cd
author_url
https://medium.com/@mukesh.vast
status
ok
fetched_at
2026-08-10 08:44:23