How to create an Nginx instance in AWS using Terraform

Basitha Gamage
AWS Tip
Published in
7 min readJan 29, 2022

--

I will assume you are already familiar with AWS, Terraform, Terminal, and cloud computing.

What is Terraform?

In case you are not familiar with what Terraform is, Terraform is an open-source Infrastructure as a Code software tool. Provisioning your infrastructure using code allow operations engineers (DevOps) to automate and repeat these tasks with minimum effort. You can find more information by visiting Hashicorps terraform website.

How to

What you need:

  1. An active AWS account. It is best practice not to use the root user. Create an IAM user with admin privileges.
  2. Have terraform installed on your machine that you will be using to write your code. If you are using VS Code, install the terraform extension. It helps with most of the autocompletes.
  3. Install AWS CLI on your machine and configure it with the IAM user credentials.

Step 1: Configure AWS CLI and provide the following information when prompted. You can find more information on the AWS Configuration Basics document.

$ aws configureAWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE 
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: us-west-2
Default output format [None]: json

Step 2: Create a main.tf file and add the following code block. We will add more code to this file as we progress. Unless specifically mentioned, all code from the rest of the steps is added to the main.tf file.

terraform {
required_providers {
aws = {
version = ">= <Version you want to use>"
source = "hashicorp/aws"
}
}
}
provider "aws" {
profile = "default"
region = "us-west-2"
}

Step 3: Assuming you want to run this instance in a new AWS VPC, we will create a new AWS VPC.

resource "aws_vpc" "nginx-vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = "true"
enable_dns_hostnames = "true"
enable_classiclink = "false"
instance_tenancy = "default"
}

Step 4: Create a public subnet for the VPC we created above.

resource "aws_subnet" "prod-subnet-public-1" {
vpc_id = aws_vpc.nginx-vpc.id // Referencing the id of the VPC from abouve code block
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = "true" // Makes this a public subnet
availability_zone = "us-west-2a"
}

Step 5: Create an Internet Gateway for the VPC. The VPC require an IGW to communicate over the internet.

resource "aws_internet_gateway" "prod-igw" {
vpc_id = aws_vpc.nginx-vpc.id
}

Step 6: Create a custom route table for the VPC.

resource "aws_route_table" "prod-public-crt" {
vpc_id = aws_vpc.nginx-vpc.id
route {
cidr_block = "0.0.0.0/0" //associated subnet can reach everywhere
gateway_id = aws_internet_gateway.prod-igw.id //CRT uses this IGW to reach internet
}
tags = {
Name = "prod-public-crt"
}
}

Step 7: Associate the route table with the public subnet.

resource "aws_route_table_association" "prod-crta-public-subnet-1" {
subnet_id = aws_subnet.prod-subnet-public-1.id
route_table_id = aws_route_table.prod-public-crt.id
}

Step 8: Create a security group to allow SSH access and HTTP access.

resource "aws_security_group" "ssh-allowed" {vpc_id = aws_vpc.nginx-vpc.idegress {
from_port = 0
to_port = 0
protocol = -1
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] // Ideally best to use your machines' IP. However if it is dynamic you will need to change this in the vpc every so often.
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}

Step 9: Create a variables.tf file and add the following variables.

variable "PRIVATE_KEY_PATH" {
default = "aws-key"
}
variable "PUBLIC_KEY_PATH" {
default = "aws-key.pub"
}
variable "EC2_USER" {
default = "ubuntu"
}

Step 10: Add the following code block in the main.tf file to associate an SSH public key with the AWS EC2 instance.

resource "aws_key_pair" "aws-key" {
key_name = "aws-key"
public_key = file(var.PUBLIC_KEY_PATH)// Path is in the variables file
}

Step 11: We will use Terraform Provisioners to configure our AWS Nginx server in our AWS EC2. Terraform documentation strongly suggests not using Provisioners unless it is the only way to do it. Typically provisioning would be handover to another tool such as Ansible. However, doing so is beyond the scope of this tutorial. So, we will use Provisioners. Therefore, we need a shell script to configure Nginx. Create an nginx.sh file and copy the following code block.

#!/bin/bash# sleep until instance is ready
until [[ -f /var/lib/cloud/instance/boot-finished ]]; do
sleep 1
done
# install nginx
apt-get update
apt-get -y install nginx
# make sure nginx is started
service nginx start

Step 12: Add the following code block to main.tf file to create the EC2 instance and configure it with an Nginx server.

resource "aws_instance" "nginx_server" {
ami = "ami-08d70e59c07c61a3a"
instance_type = "t2.micro"
tags = {
Name = "nginx_server"
}
# VPC
subnet_id = aws_subnet.prod-subnet-public-1.id
# Security Group
vpc_security_group_ids = ["${aws_security_group.ssh-allowed.id}"]
# the Public SSH key
key_name = aws_key_pair.aws-key.id
# nginx installation
# storing the nginx.sh file in the EC2 instnace
provisioner "file" {
source = "nginx.sh"
destination = "/tmp/nginx.sh"
}
# Exicuting the nginx.sh file
# Terraform does not reccomend this method becuase Terraform state file cannot track what the scrip is provissioning
provisioner "remote-exec" {
inline = [
"chmod +x /tmp/nginx.sh",
"sudo /tmp/nginx.sh"
]
}
# Setting up the ssh connection to install the nginx server
connection {
type = "ssh"
host = self.public_ip
user = "ubuntu"
private_key = file("${var.PRIVATE_KEY_PATH}")
}
}

Step 13: Open the terminal and run the following commands.

$ terraform init // initialise terraform$ terraform fmt // format the code$ terraform plan // This will show you what resources terraform will create$ terraform apply // This will create all the resources in your AWS account

Step 14: Log into your AWS account, and you should see the nginx_server EC2.

Below is the complete main.tf file

# Defining providers we will use in this deployement
# We only need the aws provides. Terraform will retrive the aws provider from hashicorp bublic registry
provider "aws" {
profile = "default"
region = "us-west-2"
}
# First we need to create an aws VPC. We need a VPC to run EC2 ubuntu ami
# I am going to run an ami-08d70e59c07c61a3a with t2.micro instnace_type to keep things free
# ami ids change from region to region. You can find them here https://cloud-images.ubuntu.com/locator/ec2/
# Creating the VPC
# The code block below uses the aws_vpc module from the aws provider in hashicorp registry
# Documentation available here: https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest
resource "aws_vpc" "nginx-vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = "true"
enable_dns_hostnames = "true"
enable_classiclink = "false"
instance_tenancy = "default"
}# Creating a public subnet
# Public subnet is required for the instances in the VPC to communicate over the internet
# Documentation is available here: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet
resource "aws_subnet" "prod-subnet-public-1" {
vpc_id = aws_vpc.nginx-vpc.id // Referencing the id of the VPC from abouve code block
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = "true" // Makes this a public subnet
availability_zone = "us-west-2a"
}# Creating an Internet Gateway
# Require for the VPC to communicate with the internet
# Documentation is available here: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway
resource "aws_internet_gateway" "prod-igw" {
vpc_id = aws_vpc.nginx-vpc.id
}# Create a custom route table for public subnets
# Public subnets can reach to the internet buy using this
# Documentation is available here: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table
resource "aws_route_table" "prod-public-crt" {
vpc_id = aws_vpc.nginx-vpc.id
route {
cidr_block = "0.0.0.0/0" //associated subnet can reach everywhere
gateway_id = aws_internet_gateway.prod-igw.id //CRT uses this IGW to reach internet
}
tags = {
Name = "prod-public-crt"
}
}
# Route table association for the public subnets
# Documentation is available here: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association
resource "aws_route_table_association" "prod-crta-public-subnet-1" {
subnet_id = aws_subnet.prod-subnet-public-1.id
route_table_id = aws_route_table.prod-public-crt.id
}
# Security group
# Creating this so we can SSH into the VPC and the ami instance to instal nginx port 22
# This also allow us to access the nginx server via the public ip address port 80
# Documentation is available here: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
resource "aws_security_group" "ssh-allowed" {
vpc_id = aws_vpc.nginx-vpc.idegress {
from_port = 0
to_port = 0
protocol = -1
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] // Ideally best to use your laptops IP. However if it is dynamic you will need to change this in the vpc every so often.
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}# Setting up the aws ssh key. You need to generate one and store it in the same directory
# We use this to establish the SSH connection to install nginx using remore-exec
# I think these are best handles using the terraform cloud workspace. I'm not sure how to do that yet.
# Documentation is available here: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair
resource "aws_key_pair" "aws-key" {
key_name = "aws-key"
public_key = file(var.PUBLIC_KEY_PATH)
}
# Setting up the EC2 instnace
# We are installing ubunto as the core OD
resource "aws_instance" "nginx_server" {
ami = "ami-08d70e59c07c61a3a"
instance_type = "t2.micro"
tags = {
Name = "nginx_server"
}
# VPC
subnet_id = aws_subnet.prod-subnet-public-1.id
# Security Group
vpc_security_group_ids = ["${aws_security_group.ssh-allowed.id}"]
# the Public SSH key
key_name = aws_key_pair.aws-key.id
# nginx installation
# storing the nginx.sh file in the EC2 instnace
provisioner "file" {
source = "nginx.sh"
destination = "/tmp/nginx.sh"
}
# Exicuting the nginx.sh file
# Terraform does not reccomend this method becuase Terraform state file cannot track what the scrip is provissioning
provisioner "remote-exec" {
inline = [
"chmod +x /tmp/nginx.sh",
"sudo /tmp/nginx.sh"
]
}
# Setting up the ssh connection to install the nginx server
connection {
type = "ssh"
host = self.public_ip
user = "ubuntu"
private_key = file("${var.PRIVATE_KEY_PATH}")
}
}

Good luck!

--

--

A Digital Engineering Consultant in the making. Absolutely fascinated about cloud computing, and what technology holds for us in the future.