How to Deploy an EC2 instance using Terraform

Amazon Elastic Compute Engine (EC2) is a web-based service from Amazon which allow users to rent virtual compute services on which to run their own compute services.

Terraform is an open-source infrastructure as code software tool that provides a consistent CLI workflow to manage hundreds of cloud services. Terraform codifies cloud APIs into declarative configuration files.

In this tutorial, I will work you through the stages of deploying an Ec2 instance using Terraform. We will deploy an Ubuntu server and in turn enable an Nginx on it.

Steps to follow

Before we can proceed make sure you have an AWS account all set up and also you are familiar with Terraform commands init, plan and deploy.

  1. Create an AWS account
  2. Install Terraform on your machine be it on Linux, Windows, or macOS. For This tutorial, i will be using Ubuntu 22.04
  3. Create a Virtual Private Cloud VPC
  4. Create Internet Gateway
  5. Create route table
  6. Create a subnet
  7. Associate created subnet with route table you created earlier.
  8. Create a security group to allow SSH, HTTPS, and HTTP services
  9. Create a network interface with an IP in the subnet created earlier.
  10. Assign an elastic IP to the network interface
  11. Create an Ubuntu server and install Nginx

Before we can proceed let’s recap on Terraform’s useful commands.

  • Terraform init command is used to initialize a working directory containing Terraform working files.
  • Terraform plan command creates an execution plan and lets you preview the changes that will be affected once you apply.
  • Terraform apply command executes the action proposed by the terraform plan command.
  • Terraform destroy command deletes/destroy all the remote infrastructure managed by Terraform.

1. Create an AWS account.

To create an AWS account head over to aws.amazon and register your details. This step is easy to do.

2. Install Terraform on Ubuntu 22.04

We need to install Terraform first before we can proceed. I have covered how to install Terraform on ubuntu earlier. So check it out here.

3. Create a VPC

Amazon Virtual Private Cloud (Amazon VPC) enables you to launch AWS resources into a virtual private network that is already defined.

To create a VPC in Terraform we are going to use VSCode to define our resources. So head over to VSCode and create a folder named Terraform_projects. Cd into this folder and create a file called main.tf. tf is an extension file for Terraform.

$ sudo mkdir terraform_projects
$ cd terraform_projects 
$ touch main.tf
$ code .

To create a Terraform VPC, open main.tf file and paste the following code.

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

  required_version = ">= 1.2.0"
}
# Provider, it configures the specified provider here aws

provider "aws" {
    region = "us-east-1" # region where your server will be located 
    access_key = "Access key" # access key, this comes from AWS account 
    secret_key = "Your secret access key" # secret key comes from AWS Account
}
# Create a VPC 

resource "aws_vpc" "prod-vpc" {
    cidr_block = "10.0.0.0/24" # IP address range 

    tags = { # This specifies the name to be displayed on aws console 
      "Name" = "Prod-VPC"
    }
  
}

4. Create an internet Gateway

Internet gateway enables resources such as EC2 instances in your public subnets to connect to the internet if the public IPv4 address or IPv6 is set.

To create a Terraform internet gateway resource we use the following configurations.

# create an internet gateway

resource "aws_internet_gateway" "prod-gateway" {
    vpc_id = aws_vpc.prod-vpc.id #comes from the resource "aws_vpc" "prod-vpc"

    tags = {
      "Name" = "Prod_Gateway"
    }
  
}

5. Create a route table

A route table contains a set of rules called routes that determine where traffic from your subnet or gateway is directed to.

To create a route table head over to your Vscode and continue from where we left. Paste the following code in main.tf file.

# create a route table

resource "aws_route_table" "prod-route-table" {
  vpc_id = aws_vpc.prod-vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.prod-gateway.id
  }

  route {
    ipv6_cidr_block = "::/0"
    gateway_id = aws_internet_gateway.prod-gateway
  }

  tags = {
    "Name" = "Prod-Route"
  }
  
}

6. Create a subnet

A subnet is a range of IP addresses in a VPC. To use subnets you can specify a range in your configuration files.

To create a subnet resource, paste the following code into vscode as a continuation.

# Create a subnet 

resource "aws_subnet" "prod-subnet0" {
  vpc_id = aws_vpc.prod-vpc.id
  availability_zone = "us-east-1c"
  cidr_block = "10.0.1.0/24"

  tags = {
    "Name" = "Prod-Subnet"
  }
  
}

7. Associate created subnet with route table

Route table association provides a resource to create an association between a route table and a subnet or internet gateway.

# Associate subnet with a route table

resource "aws_route_table_association" "Prod-associate" {
  subnet_id      = aws_subnet.prod-subnet0.id
  route_table_id = aws_route_table.prod-route-table.id
}

8. Create a security group to allow SSH, HTTPS, and HTTP services

Securing your network is a mandatory thing to avoid any eavesdropping. We are going to enable ssh on port 22, HTTP on port 80, and HTTPS on port 443

# Create a security Group to allow traffc on port 22, 80, 443


resource "aws_security_group" "prod-allow_tls" {
  name        = "prod-allow_tls"
  description = "Allow TLS inbound traffic"
  vpc_id      = aws_vpc.prod-vpc.id

  ingress {
    description      = "HTTPS"
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

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

   ingress {
    description      = "SSH"
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

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

  tags = {
    Name = "Prod-Allow_tls"
  }
}

9. Create a network interface

An elastic network interface is a logical networking component in a VPC that represents a virtual network card. To create a VPC we will use the following code.

# Create a network interface with an IP in the subnet that was created earlier

resource "aws_network_interface" "prod-nic" {
  subnet_id       = aws_subnet.prod-subnet0.id
  private_ips     = ["10.0.1.20"]
  security_groups = [aws_security_group.prod-allow_tls.id]
}

10. Assign an elastic IP to the network interface

An elastic Ip address in AWS in the public IP address. So assign one to the network interface using the following code.

# Assign an elastic IP address to the network interface

resource "aws_eip" "prod-eip" {
  vpc                       = true
  network_interface         = aws_network_interface.prod-nic.id
  associate_with_private_ip = "10.0.1.20"
  depends_on = [aws_internet_gateway.prod-gateway]

11. Create an Ubuntu server and install Nginx

The remaining thing now is to create an ubuntu server. Go to AWS console, running instances, click launch instance and search for Ubuntu and copy the AMI ID.

Create a key.pem file from your AWS console. Go

ec2 > key pair > create key pair > give a name and hit create

The key pair will be downloaded.

# Create an Ubuntu server and install Nginx 

resource "aws_instance" "web-server-instance" {
    ami = "ami-052efdf9da"
    instance_type = "t2.micro"
    availability_zone = "us-east-1c"
    key_name = "prod-key"

    network_interface {
        device_index = 0
        network_interface_id = aws_network_interface.prod-nic.id
      
    }

    user_data = <<-EOF
                #!/bin/bash
                sudo apt update -y
                sudo apt install nginx -y
                sudo systemctl start nginx
                sudo bash -c 'This is my first Nginx server > /var/www/html/index.html'
                EOF

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

  
}

We have finished creating our infrastructure, it’s now time to initialize the environment.

12. Initialize the environment.

We are going to use terraform init command to start initialization.

terraform init

You will see an output like this.

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 4.16"...
- Installing hashicorp/aws v4.19.0...
- Installed hashicorp/aws v4.19.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

The next command to use is terraform plan

terraform plan

You will see an output like this

# Sample output.
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_eip.prod-eip will be created
  + resource "aws_eip" "prod-eip" {
      + allocation_id             = (known after apply)
      + associate_with_private_ip = "10.0.1.20"
      + association_id            = (known after apply)
      + carrier_ip                = (known after apply)
      + customer_owned_ip         = (known after apply)
      + domain                    = (known after apply)
      + id                        = (known after apply)
      + instance                  = (known after apply)
      + network_border_group      = (known after apply)
      + network_interface         = (known after apply)
      + private_dns               = (known after apply)
      + private_ip                = (known after apply)
      + public_dns                = (known after apply)
      + public_ip                 = (known after apply)
      + public_ipv4_pool          = (known after apply)
      + tags_all                  = (known after apply)
      + vpc                       = true
    }

The plus sign shows that it will be added once you hit apply.

Let’s now apply our changes. We are going to use terraform apply.

terraform apply

When prompted click yes to allow the changes to take effect.

Sample output.
Plan: 9 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.prod-vpc: Creating...
aws_vpc.prod-vpc: Creation complete after 4s [id=vpc-07f54c3339d0775c1]
aws_subnet.prod-subnet0: Creating...
aws_internet_gateway.prod-gateway: Creating...
aws_security_group.prod-allow_tls: Creating...
aws_subnet.prod-subnet0: Creation complete after 2s [id=subnet-0044a170cf5c43058]
aws_internet_gateway.prod-gateway: Creation complete after 2s [id=igw-08b89fed03e5f826a]
aws_route_table.prod-route-table: Creating...
aws_route_table.prod-route-table: Creation complete after 3s [id=rtb-0d9116dfe11170024]
aws_route_table_association.Prod-associate: Creating...
aws_security_group.prod-allow_tls: Creation complete after 6s [id=sg-0cd6c56acfa6e3cac]
aws_network_interface.prod-nic: Creating...
aws_route_table_association.Prod-associate: Creation complete after 2s [id=rtbassoc-04899c139406df489]
aws_network_interface.prod-nic: Creation complete after 1s [id=eni-09555567dabe65b39]
aws_eip.prod-eip: Creating...
aws_instance.web-server-instance: Creating...
aws_eip.prod-eip: Creation complete after 2s [id=eipalloc-020315147d812b2be]
aws_instance.web-server-instance: Still creating... [10s elapsed]
aws_instance.web-server-instance: Still creating... [20s elapsed]
aws_instance.web-server-instance: Still creating... [30s elapsed]
aws_instance.web-server-instance: Creation complete after 36s [id=i-02b8bd47acb9f4db5]

Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

If you go now to your AWS EC2 instance you will see a running server

You can now look for the assigned elastic IP address and paste it into your browser.

Lastly, before we can destroy the infrastructure lets ssh into the Ubuntu server.

Ec2 instnce > connect > ssh client, then follow the prompts.
SSH client

Check to see if Nginx is running.

We cn now destroy our infrastructure with terraform destroy command.

terraform destroy

Sample output

Plan: 0 to add, 0 to change, 9 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_route_table_association.Prod-associate: Destroying... [id=rtbassoc-04899c139406df489]
aws_eip.prod-eip: Destroying... [id=eipalloc-020315147d812b2be]
aws_instance.web-server-instance: Destroying... [id=i-02b8bd47acb9f4db5]
aws_route_table_association.Prod-associate: Destruction complete after 1s
aws_route_table.prod-route-table: Destroying... [id=rtb-0d9116dfe11170024]
aws_route_table.prod-route-table: Destruction complete after 1s
aws_eip.prod-eip: Destruction complete after 3s
aws_internet_gateway.prod-gateway: Destroying... [id=igw-08b89fed03e5f826a]
aws_internet_gateway.prod-gateway: Destruction complete after 2s
aws_instance.web-server-instance: Still destroying... [id=i-02b8bd47acb9f4db5, 10s elapsed]
aws_instance.web-server-instance: Still destroying... [id=i-02b8bd47acb9f4db5, 20s elapsed]
aws_instance.web-server-instance: Still destroying... [id=i-02b8bd47acb9f4db5, 30s elapsed]
aws_instance.web-server-instance: Destruction complete after 32s
aws_network_interface.prod-nic: Destroying... [id=eni-09555567dabe65b39]
aws_network_interface.prod-nic: Destruction complete after 1s
aws_subnet.prod-subnet0: Destroying... [id=subnet-0044a170cf5c43058]
aws_security_group.prod-allow_tls: Destroying... [id=sg-0cd6c56acfa6e3cac]
aws_subnet.prod-subnet0: Destruction complete after 1s
aws_security_group.prod-allow_tls: Destruction complete after 2s
aws_vpc.prod-vpc: Destroying... [id=vpc-07f54c3339d0775c1]
aws_vpc.prod-vpc: Destruction complete after 0s

Destroy complete! Resources: 9 destroyed.

If you head over to AWS Ec2 instance you will see terminated state.

About Kipkoech Sang

I am a technology enthusiast who loves to share gained knowledge through offering daily tips as a way of empowering others. I am fan of Linux and all other things open source.
View all posts by Kipkoech Sang →

Leave a Reply

Your email address will not be published.