DEV Community

Sai Min Thu
Sai Min Thu

Posted on

Deploy a scalable web application on AWS, demonstrating full Infrastructure as Code (IaC) principles.

Flowchart
This flowchart outlines the process of deploying innnomax.space web application.

  1. Code & Repository: You will create a simple web application and push it to a GitHub repository.
  2. Terraform Configuration: You will write Terraform code to define your infrastructure.
  3. AWS & Terraform: Terraform will interact with the AWS API to provision resources.
  4. Provisioning: The following AWS resources will be created: o Amazon Linux EC2 Instance: This is where your web app will run. o Security Group: This acts as a virtual firewall, allowing web traffic (HTTP/HTTPS). o Elastic IP (EIP): This provides a static public IP address for your instance.
  5. DNS & Domain: The Elastic IP will be associated with your domain name, innomax.space, using your domain registrar's DNS settings. “Z.com”
  6. User Data Script: A user data script will be used to automatically install the necessary software on the EC2 instance, including Git and Nginx, and to clone your repository from GitHub.
  7. Web Application: Your web application will be accessible via your domain name.

Prerequisites
• AWS Account: You must have an AWS account with an Access Key and Secret Access Key.
• Terraform: Download and install Terraform on your local machine.
• Git: Download and install Git on your local machine.
• GitHub Account: Create a GitHub account.
• Domain Name: Your domain name, innomax.space, registered with a registrar like Z.COM.


Lab Steps

Step 1: Create a GitHub Repository & Web Page

  1. Log in to your GitHub account and create a new public repository named innomax-web-app. Do not initialize it with a README file.
  2. On your local machine, create a new folder and initialize it as a Git repository.
Bash
mkdir innomax-web-app
cd innomax-web-app
git init
Enter fullscreen mode Exit fullscreen mode
  1. Create a simple HTML file named “index.html” inside the folder.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome to Innomax!</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body {
            font-family: 'Inter', sans-serif;
        }
    </style>
</head>
<body class="bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center min-h-screen p-4">
    <div class="bg-white rounded-3xl shadow-2xl p-8 md:p-12 max-w-xl w-full text-center transform transition-all duration-500 hover:scale-105">
        <div class="flex flex-col items-center">
            <svg class="h-16 w-16 text-indigo-600 mb-4 animate-bounce" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9.75 18a.75.75 0 00.75.75h3a.75.75 0 00.75-.75v-1.5m-3.75 0V9.75m3.75 0V17m-3.75 0h3.75m-3.75 0h3.75m0 0a3 3 0 11-6 0 3 3 0 016 0zM12 21a9 9 0 100-18 9 9 0 000 18z" />
            </svg>
            <h1 class="text-5xl font-extrabold text-gray-800 mb-4">Hello from Innomax!</h1>
        </div>
        <p class="text-xl text-gray-600 mb-6">
            This beautiful web application is hosted on an Amazon Linux EC2 instance.
        </p>
        <p class="text-md text-gray-500 mb-8">
            The entire infrastructure for this page was defined as code with <span class="font-semibold text-teal-600">Terraform</span> and deployed automatically.
        </p>

        <!-- Social Links Section -->
        <div class="flex flex-wrap justify-center gap-4 mb-8">
            <a href="https://web.facebook.com/HugoClouds" target="_blank" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full shadow-lg transition-all duration-300 transform hover:scale-105">
                Facebook
            </a>
            <a href="https://www.linkedin.com/in/saiminthu" target="_blank" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-full shadow-lg transition-all duration-300 transform hover:scale-105">
                LinkedIn
            </a>
            <a href="https://www.youtube.com/@saiminthuu" target="_blank" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-full shadow-lg transition-all duration-300 transform hover:scale-105">
                YouTube
            </a>
            <a href="https://www.notiz.life" target="_blank" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-full shadow-lg transition-all duration-300 transform hover:scale-105">
                My App
            </a>
        </div>

        <div class="relative inline-block w-full">
            <button onclick="window.location.reload();" class="w-full md:w-auto bg-indigo-600 text-white font-bold py-3 px-8 rounded-full shadow-lg hover:bg-indigo-700 transition-all duration-300 transform hover:scale-105 focus:outline-none focus:ring-4 focus:ring-indigo-300">
                Refresh Page
            </button>
        </div>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

  1. Add the file, commit the changes, and push it to your GitHub repository.
Bash
git add .
git commit -m "version 01"
git remote add origin https://github.com/SaiThuHugoCloud/innomax-web-app.git
git push origin master
Enter fullscreen mode Exit fullscreen mode

We need to generate the ssh in our local VM.

ssh-keygen –t rsa –f ~/.ssh/my-innoweb-key

Note (The command ssh-keygen is a tool for creating, managing, and converting authentication keys for SSH.
ssh-keygen: This is the command that initiates the key generation process.
-t rsa: This flag specifies the type of key to create. rsa is a widely used and secure algorithm for generating key pairs. Other options include ed25519 and dsa.
-f ~/.ssh/my-innoweb-key: This flag specifies the filename and location where the key pair will be saved.
~/.ssh/: This is the standard, default directory for storing SSH keys on Linux and macOS. The ~ symbol is a shortcut for the current user's home directory.
my-innomax-key: This is the base name for the key files. The command will create two files:

my-innoweb-key (the private key)

my-innoweb-key.pub (the public key)

Step 2: Write Terraform Configuration Files

Create a new directory for your Terraform files and place the following files inside it.
variables.tf

variable "aws_region" {                               # Define variable for AWS region
  description = "AWS region"                          # Description for documentation
  type        = string                                # Must be a string
  default     = "us-east-1"                           # Default region (N. Virginia)
}

variable "instance_type" {                            # Define variable for EC2 instance type
  description = "EC2 instance type"                   # Used for VM size selection
  type        = string                                # Must be a string
  default     = "t2.micro"                            # Free-tier eligible small instance
}

variable "key_name" {                                 # Define variable for EC2 key pair
  description = "EC2 key pair name"                   # Key pair to connect via SSH
  type        = string                                # Must be a string
  default     = "my-innomax-key"                      # Default key pair name
}

variable "public_key_path" {                          # Define variable for local public key file path
  description = "Path to public key file"             # Path to .pub key used for EC2 login
  type        = string                                # Must be a string
  default     = "/home/saithu/.ssh/my-innomax-key.pub" # Default location of public key
}

variable "github_repo_url" {                          # Define variable for GitHub repository
  description = "GitHub repo with web app"            # Repository that stores web application code
  type        = string                                # Must be a string
  default     = "https://github.com/SaiThuHugoCloud/ innomax-web-app.git" # Default GitHub repo URL
}}
main.tf
terraform {                          # Configure Terraform settings
  required_providers {                # Define required providers
    aws = {                           # Specify AWS provider
      source  = "hashicorp/aws"       # Source of AWS provider (official)
      version = "6.11.0"              # Specific version of AWS provider
    }
  }
}

# AWS Provider configuration
provider "aws" {
  region  = var.aws_region            # Use region from variable
  profile = "saithu"                  # AWS CLI profile to use
}

# Load local public key file into Terraform
data "local_file" "public_key" {
  filename = var.public_key_path      # Path to public key (from variable)
}

# Create or import AWS Key Pair
resource "aws_key_pair" "generated_key" {
  key_name   = var.key_name           # Key pair name (from variable)
  public_key = data.local_file.public_key.content  # Use local public key
  lifecycle {
    prevent_destroy = true            # Prevent accidental deletion
  }
}

# Get the latest Amazon Linux 2023 AMI
data "aws_ami" "amazon_linux" {
  most_recent = true                  # Always fetch the newest version
  owners      = ["137112412989"]      # Amazon official AMI account ID

  filter {                            # Filter AMI by name pattern
    name   = "name"
    values = ["al2023-ami-2023.*-x86_64"]
  }

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

# Fetch default VPC
data "aws_vpc" "default" {
  default = true                      # Get the default VPC of account/region
}

# Get all subnets in the default VPC
data "aws_subnets" "public" {
  filter {
    name   = "vpc-id"                  # Filter by VPC ID
    values = [data.aws_vpc.default.id] # Match default VPC ID
  }
}

# Choose first subnet from the list
locals {
  public_subnet_id = tolist(data.aws_subnets.public.ids)[0]
}

# Create Security Group for web server
resource "aws_security_group" "web_sg" {
  name        = "web-server-sg"       # Security Group name
  description = "Allow HTTP, HTTPS, SSH, and ICMP"
  vpc_id      = data.aws_vpc.default.id

  ingress {                           # Allow inbound HTTP (80)
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]       # Open to all IPs
    description = "HTTP from anywhere"
  }

  ingress {                           # Allow inbound HTTPS (443)
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTPS from anywhere"
  }

  ingress {                           # Allow inbound SSH (22)
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "SSH from anywhere"
  }

  ingress {                           # Allow ICMP (ping)
    from_port   = -1
    to_port     = -1
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow ICMP (ping)"
  }

  egress {                            # Allow all outbound traffic
    from_port   = 0
    to_port     = 0
    protocol    = "-1"                # All protocols
    cidr_blocks = ["0.0.0.0/0"]       # To anywhere
  }

  tags = {
    Name = "web-server-sg"            # Tag for identification
  }
}

# IAM role for SSM (to connect EC2 via AWS Systems Manager)
resource "aws_iam_role" "ssm_role" {
  name = "ec2-ssm-role"               # IAM role name

  assume_role_policy = jsonencode({   # Trust policy for EC2
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com" # Allow EC2 to assume role
      }
    }]
  })

  lifecycle {
    prevent_destroy = true            # Protect from deletion
  }
}


# Attach SSM Managed Policy to IAM role
resource "aws_iam_role_policy_attachment" "ssm_attach" {
  role       = aws_iam_role.ssm_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

# Create Instance Profile for EC2 to use IAM role
resource "aws_iam_instance_profile" "ssm_profile" {
  name = "ec2-ssm-instance-profile-v2" # Name of instance profile
  role = aws_iam_role.ssm_role.name    # Attach SSM role

  lifecycle {
    prevent_destroy = true             # Prevent accidental deletion
  }
}

# Launch EC2 instance (web server)
resource "aws_instance" "web_server" {
  ami                    = data.aws_ami.amazon_linux.id  # Use latest Amazon Linux AMI
  instance_type          = var.instance_type             # Instance type from variable
  key_name               = aws_key_pair.generated_key.key_name
  vpc_security_group_ids = [aws_security_group.web_sg.id] # Attach Security Group
  subnet_id              = local.public_subnet_id        # Place in first subnet
  iam_instance_profile   = aws_iam_instance_profile.ssm_profile.name # Attach IAM role

  tags = {
    Name = "innomax-web-server"        # Tag for server
  }

  # User data script (runs on boot)
  user_data = <<-EOF
              #!/bin/bash
              yum update -y                     # Update system
              yum install -y nginx git          # Install nginx and git
              systemctl start nginx             # Start nginx
              systemctl enable nginx            # Enable nginx on boot
              cd /usr/share/nginx/html          # Move to nginx web root
              rm -rf *                          # Clear existing files
              git clone ${var.github_repo_url} . # Clone website from GitHub
              chown -R nginx:nginx /usr/share/nginx/html # Set permissions
              EOF
}


# Allocate Elastic IP and attach to EC2


resource "aws_eip" "web_eip" {
  instance = aws_instance.web_server.id
  tags = {
    Name = "innomax-web-eip"           # Tag for Elastic IP
  }
}

# Output the EC2 Public IP (Elastic IP)
output "web_server_public_ip" {
  value = aws_eip.web_eip.public_ip   # Show IP after apply
}
Enter fullscreen mode Exit fullscreen mode

terraform.tfvars
This file allows you to specify variable values without modifying variables.tf.
Terraform

This file sets actual values for variables (overrides defaults in variables.tf).

It helps keep your config flexible without editing the main Terraform code.

aws_region      = "us-east-1"                          # AWS region (N. Virginia)
instance_type   = "t2.micro"                           # EC2 instance type (small, free-tier eligible)
key_name        = "my-innoweb-key"                     # Name of existing EC2 key pair
public_key_path = "/home/saithu/.ssh/my-innoweb-key.pub" # Path to local public key
github_repo_url = "https://github.com/SaiThuHugoCloud/innomax-web-app.git" 
# GitHub repo to clone into the web server
Enter fullscreen mode Exit fullscreen mode

Step 3: Provision Infrastructure with Terraform

  1. Open your terminal and navigate to the directory containing your Terraform files.
  2. Initialize the Terraform project. This downloads the necessary AWS provider. terraform init
  3. Review the plan to see what Terraform will create. terraform plan
  4. Apply the configuration to create the AWS resources. terraform apply You will be prompted to confirm. Type yes and press Enter. Once the process is complete, Terraform will output the public IP address of your EC2 instance. Copy this IP address.

Step 4: Configure DNS for Your Domain

  1. Log in to your Z.COM domain registrar account.
  2. Navigate to the DNS management section for your domain, innomax.space.
  3. Create or modify an A record for your domain. o Host/Name: @ or innomax.space o Type: A o Value/IP Address: Paste the public IP address you got from the Terraform output. o TTL: Set a low TTL (e.g., 300 seconds) for faster propagation.
  4. Save the changes. It may take some time for the DNS changes to propagate globally (up to 24 hours, but often much faster).

Step 5: Test the Web Application

  1. After waiting a few minutes for DNS propagation, open your web browser.
  2. Navigate to your domain name, http://www.innomax.space.
  3. You should see the "Welcome to Innomax!” web page you created. This completes the hands-on lab. You have successfully deployed a web application using AWS, Terraform, Git, and GitHub.

Top comments (0)