In this project, I aimed to consolidate my Terraform knowledge by demonstrating best practices while provisioning AWS resources. The source code for this project can be found here!


A comprehensive guide to dockerizing and deploying both React and Node.js applications on the same AWS EC2 instance, covering the complete workflow from local development to production deployment.

Exploring the three major memory management approaches in programming: Garbage Collection, Manual Memory Management, and Rust's Ownership system.
In this project, I aimed to consolidate my Terraform knowledge by demonstrating best practices while provisioning AWS resources. The source code for this project can be found here!
— Architecture
The architecture diagram below illustrates an AWS-EC2 instance running a containerized Nginx server within a VPC, complete with a subnet and an internet gateway. To deploy our application, we need to create the following AWS resources:
10.0.0.0/16)10.0.1.0/24), and select an availability zone0.0.0.0/0 as the destination and your Internet Gateway as the target.pem) and save it securelyt2.micro)SSH into your instance using the key pair file:
ssh -i /path/to/your-key-pair.pem ubuntu@your-instance-public-ipUpdate the package list and install Docker, then pull and run the Nginx container:
sudo docker pull nginx
sudo docker run -d -p 80:80 nginxThere are two major problems:
Terraform provides a wide range of providers that can be used to provision and manage resources on different platforms such as AWS, GCP, Azure, and many others. Providers are plugins that enable Terraform to interact with APIs of cloud platforms, SaaS providers, and other services.
Download and Install Terraform from their official website.
Create a directory on your system:
mkdir my-terraform-project
cd my-terraform-project
touch main.tfCreate a providers.tf file:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.53.0"
}
}
}
provider "aws" {
region = var.aws_region
access_key = var.aws_access_key
secret_key = var.aws_secret_key
}Create a main.tf file where we will define our resources. You can find all the resources here.
resource "aws_vpc" "my-vpc" {
cidr_block = var.vpc_cidr_block
tags = {
Name = "${var.env_prefix}-${var.vpc_name}"
}
}
resource "aws_subnet" "my-subnet-1" {
vpc_id = aws_vpc.my-vpc.id
cidr_block = var.subnet_cidr_block
availability_zone = var.availability_zone
tags = {
Name = "${var.env_prefix}-${var.subnet_name}"
}
}
resource "aws_internet_gateway" "my-igw" {
vpc_id = aws_vpc.my-vpc.id
tags = {
Name = "${var.env_prefix}-my-igw"
}
}
resource "aws_route_table" "my-route-table" {
vpc_id = aws_vpc.my-vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.my-igw.id
}
tags = {
Name = "${var.env_prefix}-my-route-table"
}
}
resource "aws_route_table_association" "my-route-table-association" {
subnet_id = aws_subnet.my-subnet-1.id
route_table_id = aws_route_table.my-route-table.id
}
resource "aws_security_group" "my-sg" {
name = "${var.env_prefix}-my-sg"
vpc_id = aws_vpc.my-vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.my_ip]
}
ingress {
from_port = 8080
to_port = 8080
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"]
}
tags = {
Name = "${var.env_prefix}-my-sg"
}
}
resource "aws_key_pair" "my-key" {
key_name = var.key_name
public_key = file("${var.key_path}")
}
resource "aws_instance" "my-ec2" {
ami = data.aws_ami.latest-ubuntu-image.id
instance_type = var.instance_type
subnet_id = aws_subnet.my-subnet-1.id
key_name = var.key_name
vpc_security_group_ids = [aws_security_group.my-sg.id]
associate_public_ip_address = true
user_data = file("setup.sh")
tags = {
Name = "${var.env_prefix}-my-ec2"
}
}Define all the variables used in the configuration:
variable "aws_region" {
type = string
description = "AWS Default Region"
}
variable "aws_access_key" {
type = string
description = "AWS Admin Access Key"
}
variable "aws_secret_key" {
type = string
description = "AWS Admin Secret Key"
}
variable "vpc_cidr_block" {
type = string
description = "CIDR block for the VPC"
}
variable "vpc_name" {
type = string
description = "Name of the VPC"
}
variable "subnet_cidr_block" {
type = string
description = "CIDR block for the subnet"
}
variable "subnet_name" {
type = string
description = "Name of the subnet"
}
variable "availability_zone" {
type = string
description = "Availability Zone"
}
variable "env_prefix" {
type = string
description = "Environment Prefix"
}
variable "my_ip" {
type = string
description = "My IP"
}
variable "instance_type" {
type = string
description = "Instance Type"
}
variable "key_name" {
type = string
description = "Key Pair Name"
}
variable "key_path" {
type = string
description = "Key Pair Path"
}Store data variables fetched from AWS:
data "aws_ami" "latest-ubuntu-image" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-*"]
}
}Store output variables:
output "aws_ami_id" {
value = data.aws_ami.latest-ubuntu-image.id
}
output "public_ip" {
value = aws_instance.my-ec2.public_ip
}variable1-name = "variable1-value"
variable2-name = "variable2-value"Create setup.sh for Docker and Nginx installation:
#!/bin/bash
apt-get update
apt-get install -y docker.io
systemctl enable docker
systemctl start docker
docker pull nginx
docker run -d -p 8080:80 --name nginx-container nginxterraform initterraform plan -var-file="varvalues.tfvars"terraform apply -var-file="varvalues.tfvars"Verify the changes on your AWS Account to ensure all the resources were created successfully.
Explore the code available in this repository to understand what Terraform Modules are and how they can be used to make your code more modular. You can compare terraform modules to functions in traditional programming which can be reused by providing different inputs to get different outputs.
Thank you for reading! I hope you learned something from this blog. Do share your thoughts using comments.
Happy Learning!