🚀Deploy EC2 on AWS with Terraform & GitHub Actions (End‑to‑End Guide)
- Mohammed Juyel Haque

- Aug 25
- 2 min read
Spin up a production‑ready EC2 instance from a GitHub repo using Terraform.

📂 Project Structure
Here’s the recommended folder layout:
.
├── .github
│ └── workflows
│ └── deploy.yml # GitHub Actions workflow
├── infra
│ ├── main.tf # Root Terraform config
│ ├── variables.tf
│ ├── outputs.tf
│ └── terraform.tfvars # Variables values
└── modules
└── ec2
├── main.tf
├── variables.tf
└── outputs.tf
Github Actions(deploy.yaml)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-terraform-state-bucket1ju",
"arn:aws:s3:::my-terraform-state-bucket1ju/*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"iam:*"
],
"Resource": "*"
}
]
}
Terraform: backend & provider (infra/providers.tf)
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket1ju"
key = "ec2/terraform.tfstate"
region = "us-east-1"
encrypt = true
}
}
provider "aws" {
region = var.region
}
Terraform: variables (infra/variables.tf)
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
}
variable "ami_id" {
description = "AMI to use"
type = string
}
variable "key_name" {
description = "SSH key name"
type = string
}
Terraform: main call the ec2 module (infra/main.tf)
module "ec2_instance" {
source = "../modules/ec2"
instance_type = var.instance_type
ami_id = var.ami_id
key_name = var.key_name
}
Terraform: outputs (infra/outputs.tf)
output "ec2_public_ip" {
description = "Public IP of the EC2 instance"
value = module.ec2_instance.public_ip
}
tfvars (env/terraform.tfvars)
region = "us-east-1"
instance_type = "t2.micro"
ami_id = "ami-00ca32bbc84273381"
key_name = "test-key"
Terraform: module variables (modules/ec2/variables.tf)
variable "ami_id" {
description = "AMI id for the EC2 instance"
type = string
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "instance_name" {
description = "Tag Name for instance"
type = string
default = "terraform-ec2-juyel"
}
variable "subnet_id" {
description = "Subnet ID to launch the instance into"
type = string
default = null
}
variable "security_group_ids" {
description = "List of security group ids"
type = list(string)
default = []
}
variable "key_name" {
description = "SSH key name to attach (optional)"
type = string
default = null
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}
Terraform: module outputs (modules/ec2/outputs.tf)
output "instance_id" {
description = "EC2 instance id"
value = aws_instance.this.id
}
output "public_ip" {
description = "EC2 public IP"
value = aws_instance.this.public_ip
}
output "private_ip" {
description = "EC2 private IP"
value = aws_instance.this.private_ip
}
Terraform: main resource (modules/ec2/main.tf)
resource "aws_instance" "this" {
ami = var.ami_id
instance_type = var.instance_type
subnet_id = var.subnet_id
key_name = var.key_name
vpc_security_group_ids = var.security_group_ids
tags = merge(
{
},
var.tags
)
}
IAM Role Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-terraform-state-bucket1ju",
"arn:aws:s3:::my-terraform-state-bucket1ju/*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"iam:*"
],
"Resource": "*"
}
]
}


Comments