How to Use Terraform to Launch an AWS EC2 Instance

Step-by-step guide on How to Use Terraform to Launch an AWS EC2 Instance

Terraform is an open-source Infrastructure as Code (IaC) tool created by HashiCorp. With Terraform, you define your infrastructure using a declarative configuration language called HashiCorp Configuration Language (HCL) (or optionally JSON), then Terraform plans and applies the changes via provider APIs.

In this guide, we’ll use Terraform to provision a basic EC2 instance in AWS and output the instance IP address so you can SSH into it.

Also check:

Prerequisites

To follow along this guide, you need the following:

  • An AWS account and IAM credentials with permissions to manage EC2 (and related networking resources like security groups).
  • (Optional but recommended) AWS CLI installed. Install it here.
  • Terraform installed. Get it from here.
  • A VPC ID and a subnet ID where you want the instance to run (ideally a public subnet if you want a public IP).
  • An SSH key pair (a local private key + public key).

This is the Terraform version I am using:

1
2
3
4
$ terraform --version

Terraform v1.6.6
on darwin_arm64

Configure AWS credentials

Terraform uses the same credential discovery chain as the AWS SDK. You can use any of these approaches:

  • AWS CLI profile (recommended): run aws configure then export AWS_PROFILE.
  • Environment variables: export access key + secret key.
  • IAM role: if you’re running on an AWS environment (e.g. CloudShell/EC2), attach a role.

Here is the environment variable approach:

1
2
3
export AWS_ACCESS_KEY_ID="your-access-key-id"
export AWS_SECRET_ACCESS_KEY="your-secret-access-key"
export AWS_DEFAULT_REGION="us-west-2"

Project files

Create a new directory for this demo (for example terraform-ec2/) and add these files:

  • versions.tf for Terraform + provider requirements
  • variables.tf for inputs
  • main.tf for resources
  • outputs.tf for outputs

versions.tf (Terraform + provider requirements)

Pinning provider versions makes runs more predictable across machines and over time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
terraform {
  required_version = ">= 1.6.6"

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

variables.tf (inputs)

We’ll keep this example configurable (region, VPC, subnet, instance type, SSH CIDR, etc). Note that the SSH ingress CIDR is intentionally required so you don’t accidentally open port 22 to the whole internet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
variable "region" {
  description = "AWS region to deploy into."
  type        = string
  default     = "us-west-2"
}

variable "vpc_id" {
  description = "VPC ID where the security group will be created."
  type        = string
}

variable "subnet_id" {
  description = "Subnet ID where the EC2 instance will be launched."
  type        = string
}

variable "instance_type" {
  description = "EC2 instance type."
  type        = string
  default     = "t3.micro"
}

variable "ami_id" {
  description = "AMI ID to use for the instance (AMI IDs are region-specific)."
  type        = string
}

variable "key_name" {
  description = "Name of the EC2 key pair to create/use."
  type        = string
  default     = "citizix-demo-key"
}

variable "public_key_path" {
  description = "Path to the local SSH public key."
  type        = string
  default     = "~/.ssh/id_rsa.pub"
}

variable "allowed_ssh_cidr" {
  description = "CIDR allowed to SSH into the instance (e.g. your public IP /32)."
  type        = string
}

variable "ssh_user" {
  description = "SSH username for the selected AMI (e.g. ec2-user, ubuntu, debian)."
  type        = string
  default     = "ec2-user"
}

main.tf (provider + resources)

Adding a provider

A Terraform provider is an integration that understands how to talk to an API (AWS in our case). We’ll also configure the region via a variable.

1
2
3
provider "aws" {
  region = var.region
}

Defining the key pair

This creates an EC2 key pair in AWS from your local public key. Note that Terraform identifiers (like aws_key_pair.ssh_key) use letters, digits, and underscores (no hyphens).

1
2
3
4
resource "aws_key_pair" "ssh_key" {
  key_name   = var.key_name
  public_key = file(var.public_key_path)
}

Defining the security group

A security group acts as a virtual firewall for your EC2 instances. We’ll allow SSH (port 22) only from a single CIDR you specify, and allow all outbound traffic.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
resource "aws_security_group" "ssh_only" {
  name        = "citizix-ec2-ssh"
  description = "Allow SSH access to EC2"
  vpc_id      = var.vpc_id

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.allowed_ssh_cidr]
  }

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

  tags = {
    Name = "citizix-ec2-ssh"
  }
}

Creating the EC2 instance

Now we can create an EC2 instance. We’ll attach the security group, place it in your chosen subnet, and associate a public IP (useful for quick SSH access in a public subnet).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
resource "aws_instance" "instance" {
  ami                         = var.ami_id
  instance_type               = var.instance_type
  key_name                    = aws_key_pair.ssh_key.key_name
  vpc_security_group_ids      = [aws_security_group.ssh_only.id]
  subnet_id                   = var.subnet_id
  associate_public_ip_address = true

  root_block_device {
    volume_size           = 50
    delete_on_termination = true
  }

  tags = {
    Name = "Citizix-Server"
  }
}

outputs.tf (useful outputs)

Terraform outputs allow you to export values from your state so you can use them elsewhere. We’ll output the public/private IP plus a ready-to-copy SSH command.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
output "instance_id" {
  value = aws_instance.instance.id
}

output "instance_private_ip" {
  value = aws_instance.instance.private_ip
}

output "instance_public_ip" {
  value = aws_instance.instance.public_ip
}

output "ssh_command" {
  value = "ssh -i ~/.ssh/id_rsa ${var.ssh_user}@${aws_instance.instance.public_ip}"
}

Note: the default user depends on the AMI you choose. For example:

  • Amazon Linux: ec2-user
  • Ubuntu: ubuntu
  • Debian: admin or debian (varies by image)

terraform.tfvars (example values)

Create a terraform.tfvars file and populate your own values:

1
2
3
4
5
6
7
8
9
region           = "us-west-2"
vpc_id           = "vpc-xxxxxxxx"
subnet_id        = "subnet-xxxxxxxx"
allowed_ssh_cidr = "YOUR_PUBLIC_IP/32"
ssh_user         = "ec2-user"

# AMI IDs are region-specific.
# This was an example AMI used in an older demo; prefer looking up the latest AMI for your OS.
ami_id = "ami-0ddb956ac6be95761"

Tip: you can get your public IP and then append /32 (for example 203.0.113.10/32) to restrict SSH to only your current IP.

Creating resources

With the files in place, let’s create the AWS resources:

1
2
3
terraform init
terraform plan
terraform apply

After apply completes, Terraform will print outputs (including the public IP). You can also view outputs any time with:

1
2
3
terraform output
terraform output -raw instance_public_ip
terraform output -raw ssh_command

Conclusion

In this guide, we provisioned an EC2 instance with Terraform, restricted SSH access via a security group, and exported useful outputs (including the public IP). From here you can extend the configuration by adding a proper VPC, IAM roles, EBS encryption, user-data bootstrapping, and tighter network rules.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy