Skip to main content
Deploy AWS EC2 Instance with Terraform - Step-by-Step Guide

Deploy AWS EC2 Instance with Terraform - Step-by-Step Guide

Key Takeaway

Deploy an AWS EC2 instance with Terraform step by step. Complete guide with VPC, security groups, key pairs, user data, and production-ready configuration.

Table of Contents

Quick Answer

resource "aws_instance" "web" {
  ami           = "ami-0c7217cdde317cfec"  # Ubuntu 22.04
  instance_type = "t3.micro"
  tags          = { Name = "my-server" }
}

Run terraform init && terraform apply to launch it. For production, add a VPC, security group, and key pair as shown below.

Prerequisites

  • AWS account with IAM credentials configured
  • Terraform installed (terraform version)
  • AWS CLI configured (aws configure)

Step 1: Provider Configuration

# main.tf
terraform {
  required_version = ">= 1.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}

variable "region" {
  default = "us-east-1"
}

Step 2: Dynamic AMI Lookup

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]  # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

Step 3: Networking (VPC + Subnet)

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags                 = { Name = "main-vpc" }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "${var.region}a"
  tags                    = { Name = "public-subnet" }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

Step 4: Security Group

resource "aws_security_group" "web" {
  name   = "web-server"
  vpc_id = aws_vpc.main.id

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.my_ip]  # Restrict to your IP
  }

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

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

variable "my_ip" {
  description = "Your IP for SSH access (e.g., 1.2.3.4/32)"
  type        = string
}

Step 5: Key Pair

resource "aws_key_pair" "deploy" {
  key_name   = "deploy-key"
  public_key = file("~/.ssh/id_rsa.pub")
}

Step 6: EC2 Instance

resource "aws_instance" "web" {
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]
  key_name               = aws_key_pair.deploy.key_name

  user_data = <<-EOF
    #!/bin/bash
    apt-get update -y
    apt-get install -y nginx
    systemctl enable nginx
    systemctl start nginx
  EOF

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  tags = {
    Name        = "web-server"
    Environment = "dev"
  }
}

Step 7: Outputs

output "public_ip" {
  value = aws_instance.web.public_ip
}

output "public_dns" {
  value = aws_instance.web.public_dns
}

output "ssh_command" {
  value = "ssh ubuntu@${aws_instance.web.public_ip}"
}

Deploy

terraform init
terraform plan -var="my_ip=$(curl -s ifconfig.me)/32"
terraform apply -var="my_ip=$(curl -s ifconfig.me)/32"

# SSH into the instance
ssh ubuntu@$(terraform output -raw public_ip)

# Clean up
terraform destroy

Common Instance Types

TypevCPURAMUse CaseCost/month
t3.micro21 GBDev/test~$7.50
t3.small22 GBSmall apps~$15
t3.medium24 GBWeb servers~$30
m6i.large28 GBProduction~$70

Conclusion

Deploy EC2 instances with a VPC, security group, and key pair for production readiness. Use aws_ami data source instead of hardcoded AMI IDs, encrypt EBS volumes, restrict SSH to your IP, and always run terraform plan before apply.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.