DEV Community

Cover image for πŸ—οΈ Provision AWS EC2 Instances with Terraform and Set Up Docker via User Data
Lester Diaz Perez
Lester Diaz Perez

Posted on

2 1 1 1 1

πŸ—οΈ Provision AWS EC2 Instances with Terraform and Set Up Docker via User Data

πŸ“‹Workflow

1️⃣ πŸ“ Structure
2️⃣ 🌱 Root module
3️⃣ πŸ’» EC2 module
4️⃣ πŸ›‘οΈ Security Group module

πŸ”—Link to project


1️⃣ πŸ“ Structure

β”œβ”€β”€ dev.tfvars
β”œβ”€β”€ main.tf
β”œβ”€β”€ provider.tf
β”œβ”€β”€ modules
β”‚   β”œβ”€β”€ ec2
β”‚   β”‚   β”œβ”€β”€ install_docker.sh
β”‚   β”‚   β”œβ”€β”€ main.tf
β”‚   β”‚   β”œβ”€β”€ output.tf
β”‚   β”‚   └── variables.tf
β”‚   └── security_group
β”‚       β”œβ”€β”€ igw.tf
β”‚       β”œβ”€β”€ output.tf
β”‚       β”œβ”€β”€ rt.tf
β”‚       β”œβ”€β”€ sg.tf
β”‚       β”œβ”€β”€ subnet.tf
β”‚       └── vpc.tf

Enter fullscreen mode Exit fullscreen mode

Why it's important to split terraform into modules?

  1. πŸ”„ Code reuse
  2. πŸ“ˆ Improved scalability
  3. 🧩 Modularity and abstraction
  4. πŸ‘₯ Clear separation of responsibilities.
  5. πŸ› οΈ Simplified maintenance
  6. πŸ“– Improved readability
  7. πŸ”„ Efficient version control

2️⃣ 🌱 Root module

# main.tf
variable "ami" {}
variable "instance_type" {}
variable "key_name" {}


#Replace the resource with a module
module "ec2_instance" {
  #References file location of new module
  source = "./modules/ec2"

  ami = var.ami
  instance_type = var.instance_type
  key_name = var.key_name

  subnet_id = module.security_group.subnet_id  # Call the name of variable output 

  sg_id = module.security_group.sg_id


}

# Specify the name for the security group
module "security_group" {
  source = "./modules/security_group"

}
Enter fullscreen mode Exit fullscreen mode
#provider.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.52.0"
    }
  }

}


provider "aws" {
  region  = "us-east-2"
}
Enter fullscreen mode Exit fullscreen mode
# dev.tfvars
ami = "ami-0b4624933067d393a" # each ami is region specific 
instance_type = "t2.micro"
key_name = "ec2_key"
Enter fullscreen mode Exit fullscreen mode

3️⃣ πŸ’» EC2 module

# modules/ec2/main.tf
variable "ami" {}
variable "instance_type" {}
variable "key_name" {}

resource "aws_instance" "ec2" {
  ami           = var.ami
  instance_type = var.instance_type
  key_name      = var.key_name

  vpc_security_group_ids = [var.sg_id]
  subnet_id = var.subnet_id
  user_data = "${file("./modules/ec2/install_docker.sh")}"

  tags = {
    Name = "traefik-demo"
  }
}

Enter fullscreen mode Exit fullscreen mode
# modules/ec2/variables.tf
variable "sg_id" {
  description = "Security Group ID"
  type        = string
}
variable "subnet_id" {
  description = "Subnet ID"
  type        = string
}
Enter fullscreen mode Exit fullscreen mode

πŸ”‘ Key points.
The EC2 module requires two essential fields:

  • πŸ›‘οΈ Security Group ID.
  • 🌐 Subnet ID
# modules/ec2/output.tf
output "instance_id" {
  value = aws_instance.ec2.id
}

output "public_ip" {
  value = aws_instance.ec2.public_ip
}
Enter fullscreen mode Exit fullscreen mode
#!/bin/bash
# Update and install Docker
sudo yum update -y
sudo yum install -y jq docker


# Enable Docker service
sudo service docker start
sudo usermod -a -G docker ec2-user

# Get the latest version of Docker Compose 
DOCKER_COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r .tag_name)

# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
Enter fullscreen mode Exit fullscreen mode

4️⃣ πŸ›‘οΈ Security Group module

vpc.tf

#modules/security_group/vpc.tf
resource "aws_vpc" "traefik_vpc" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "main-vpc"
  }
}
Enter fullscreen mode Exit fullscreen mode
#modules/security_group/subnet.tf
resource "aws_subnet" "traefik_subnet" {
    vpc_id                  = aws_vpc.traefik_vpc.id
    cidr_block              = "10.0.1.0/24"
    map_public_ip_on_launch = true

    tags = {
        Name = "example-subnet"
    }
}
Enter fullscreen mode Exit fullscreen mode
#modules/security_group/sg.tf
resource "aws_security_group" "traefik_sg" {
  name        = "traefik-security-group"
  vpc_id = aws_vpc.traefik_vpc.id
  description = "Allow HTTP and HTTPS traffic"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
   ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # Allow ssh from internet
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1" # Allow all traffic
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "traefik_sg"
  }
}

Enter fullscreen mode Exit fullscreen mode
#modules/security_group/rt.tf
resource "aws_route_table" "traefik_route_table" {
  vpc_id = aws_vpc.traefik_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.traefik_igw.id
  }

  tags = {
    Name = "route_table"
  }
}
resource "aws_route_table_association" "a" {
  subnet_id      = aws_subnet.traefik_subnet.id
  route_table_id = aws_route_table.traefik_route_table.id
}
Enter fullscreen mode Exit fullscreen mode
#modules/security_group/igw.tf
resource "aws_internet_gateway" "traefik_igw" {
  vpc_id = aws_vpc.traefik_vpc.id

  tags = {
    Name = "traefik_IGW"
  }
}
Enter fullscreen mode Exit fullscreen mode
#modules/security_group/output.tf
output "subnet_id" {
  value = aws_subnet.traefik_subnet.id
}

output "sg_id" {
  value = aws_security_group.traefik_sg.id
}
Enter fullscreen mode Exit fullscreen mode

πŸ—οΈ Terraform deploy

 terraform init
 terraform validate --var-file=dev.tfvars
 terraform plan --var-file=dev.tfvars
 terraform apply --var-file=dev.tfvars -auto-approve
Enter fullscreen mode Exit fullscreen mode

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

πŸ‘‹ Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay