DEV Community

Cover image for Terraform in Action: Deploying an Ubuntu EC2 Instance with Nginx on AWS
Panchanan Panigrahi
Panchanan Panigrahi

Posted on • Updated on

Terraform in Action: Deploying an Ubuntu EC2 Instance with Nginx on AWS

Terraform is an Infrastructure as Code (IaC) tool developed by HashiCorp. It enables users to define, provision, and manage infrastructure across multiple cloud platforms like AWS, Azure, and Google Cloud using declarative configuration files. With Terraform, infrastructure can be automated, version-controlled, and shared, ensuring consistency and scalability across environments.

As organizations increasingly adopt cloud services, managing infrastructure becomes more complex. Terraform simplifies this process by allowing users to define the desired infrastructure state in code and letting the tool handle the underlying provisioning. Its multi-cloud support, robust state management, and reusable modules make Terraform a go-to solution for modern cloud infrastructure management.

Terraform Simple Commands:

  • terraform init – Initializes the working directory and downloads necessary provider plugins.
  • terraform plan – Previews the actions Terraform will take without applying changes.
  • terraform apply – Executes the changes required to reach the desired state of the configuration.
  • terraform destroy – Destroys the managed infrastructure and removes all resources defined in the configuration.

Terraform

Why Use Terraform on AWS?

Terraform offers several key benefits when managing AWS infrastructure, making it a powerful tool for developers and operations teams:

  • Multi-cloud compatibility: Terraform works across many cloud platforms, including AWS, Azure, and Google Cloud, making it easier to manage multi-cloud environments from a single tool.
  • Readable configurations: You can define your infrastructure using human-readable, declarative .tf files, which simplifies how you describe and manage your cloud resources.
  • Declarative infrastructure management: By defining the desired state of your AWS infrastructure, Terraform figures out the necessary actions to achieve that state, eliminating manual processes.
  • State management: Terraform keeps track of your current infrastructure in a state file, ensuring consistent and reliable updates and preventing infrastructure drift.
  • Change previews: With the terraform plan command, you can preview any changes before applying them, helping to catch potential issues and verify that everything works as expected.
  • Automation-friendly: Terraform integrates well with CI/CD pipelines, allowing teams to automate provisioning and infrastructure management as part of their development workflows.
  • Modular and reusable: You can break down your infrastructure into reusable modules, making it easier to standardize and manage across different environments or teams.
  • Orchestrates complex setups: Terraform handles dependencies between resources, ensuring they are created, updated, or destroyed in the right order for seamless deployments.
  • Efficient resource management: By building a dependency graph, Terraform optimizes the creation and management of resources, ensuring operations are safe, predictable, and efficient.

Deploying AWS resources using Terraform:

Using Terraform to deploy AWS resources exemplifies the power of Infrastructure as Code (IaC), making cloud infrastructure management more efficient, repeatable, and scalable. In this guide, we’ll walk you through the process of using Terraform to set up, configure, and manage AWS services in a streamlined manner.

The goal of this post is to demonstrate how to create an EC2 instance and deploy Nginx on it, turning the instance into a functional web server. By the end, you’ll have a fully automated setup where your web server is ready to serve content—illustrating the simplicity and power of IaC using Terraform.

If you get stuck at any point, you can refer to the code examples and configurations in my GitHub repo for this blog: Nginx_on_Ubuntu_EC2_Instance.

Prerequisites:

Create Terraform configuration files:

In the first step we have to tell terraform that we will be deploying infrastructure on AWS. We can do this by configuring the AWS cloud provider plugin.

Create a file main.tf, and add the below given code block.



terraform {
  required_version = ">= 1.0"

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


Enter fullscreen mode Exit fullscreen mode

This configuration tells Terraform to use the AWS provider and ensures compatibility with Terraform version 1.0 or higher. The provider version is locked to maintain stability and prevent unexpected updates.

Configure Terraform AWS provider block:

The next step is to configure the AWS provider block, which accepts various config parameters. We will start by specifying the region to deploy the infra in, us-east-1.



provider "aws" {
  region = "us-east-1"
}


Enter fullscreen mode Exit fullscreen mode

Create a vpc resource in AWS:

Next, let's create a Virtual Private Cloud (VPC) to serve as the foundation for our AWS infrastructure:



resource "aws_vpc" "main_vpc" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "main-vpc"
  }
}


Enter fullscreen mode Exit fullscreen mode

This will provision a VPC with the specified CIDR block, providing isolated networking for our resources.

Create an Internet gateway resource in AWS:

To allow internet access, we need to create an Internet Gateway and attach it to our VPC:



resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main_vpc.id
  tags = {
    Name = "main-igw"
  }
}


Enter fullscreen mode Exit fullscreen mode

This setup creates an Internet Gateway and associates it with our VPC, enabling communication between the VPC and the internet.

Create a public subnet resource in AWS:

To ensure that our VPC has public access, we'll create a public subnet within it:



resource "aws_subnet" "public_subnet" {
  vpc_id                  = aws_vpc.main_vpc.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true
  tags = {
    Name = "public-subnet"
  }
}


Enter fullscreen mode Exit fullscreen mode

This configuration sets up a subnet that will automatically assign public IP addresses to instances launched in it, enabling them to communicate with the internet.

Create a route table resource in AWS:

To ensure that our public subnet can route traffic to the internet, we'll create a route table and associate it with our subnet:



resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.main_vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
}

resource "aws_route_table_association" "public_rt_association" {
  subnet_id      = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_rt.id
}


Enter fullscreen mode Exit fullscreen mode

This configuration sets up a route table with a route to the internet through the Internet Gateway and associates it with our public subnet, enabling outgoing traffic to reach the internet.

Create and store SSH key pair using terraform:

To enable secure access to our AWS resources, we'll generate an SSH key pair. This key pair will be used for accessing instances securely:



resource "tls_private_key" "ssh_key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "local_file" "private_key" {
  content  = tls_private_key.ssh_key.private_key_pem
  filename = "./.ssh/terraform_rsa"
}

resource "local_file" "public_key" {
  content  = tls_private_key.ssh_key.public_key_openssh
  filename = "./.ssh/terraform_rsa.pub"
}


Enter fullscreen mode Exit fullscreen mode

This configuration generates an RSA key pair with a 4096-bit key length. The private and public keys are then saved to files in the .ssh directory, ready for use in connecting to our AWS instances.

Creating AWS key pair using our SSH public key:

Next, we'll create an AWS key pair using the public SSH key we generated:



resource "aws_key_pair" "deployer" {
  key_name   = "ubuntu_ssh_key"
  public_key = tls_private_key.ssh_key.public_key_openssh
}


Enter fullscreen mode Exit fullscreen mode

This resource uploads the public key to AWS, allowing you to securely access your EC2 instances using the corresponding private key.

Create a security group resource in AWS:

To control access to our AWS resources, we need to configure a security group that allows inbound traffic for SSH, HTTP, and HTTPS:



resource "aws_security_group" "allow_ssh_http_https" {
  vpc_id = aws_vpc.main_vpc.id

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

  ingress {
    from_port   = 8080
    to_port     = 8080
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    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 = "allow-ssh-https-8080"
  }
}


Enter fullscreen mode Exit fullscreen mode

This security group allows SSH (port 22), HTTP (port 8080), and HTTPS (port 443) traffic, while permitting all outbound traffic. This setup ensures that your instances can be accessed and managed securely.

Create an EC2 Instance(Ubuntu) and Install Nginx in AWS:

To provision an EC2 instance with the required configuration, we can define the following resource in Terraform:



resource "aws_instance" "ubuntu_instance" {
  ami                         = "ami-0a0e5d9c7acc336f1"
  instance_type               = "t2.micro"
  subnet_id                   = aws_subnet.public_subnet.id
  vpc_security_group_ids      = [aws_security_group.allow_ssh_http_https.id]
  key_name                    = aws_key_pair.deployer.key_name
  associate_public_ip_address = true

  depends_on = [
    aws_security_group.allow_ssh_http_https,
    aws_internet_gateway.igw
  ]

  user_data       = <<-EOF
              #!/bin/bash
              sudo apt update -y
              sudo apt install -y nginx

              # Create index.html with H1 tag in the default NGINX web directory
              echo "<h1>Hello From Ubuntu EC2 Instance!!!</h1>" | sudo tee /var/www/html/index.html

              # Update NGINX to listen on port 8080
              sudo sed -i 's/listen 80 default_server;/listen 8080 default_server;/g' /etc/nginx/sites-available/default

              # Restart NGINX to apply the changes
              sudo systemctl restart nginx
              EOF

  tags = {
    Name = "ubuntu-instance"
  }
}


Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. AMI and Instance Type:
    • The ami attribute specifies the Amazon Machine Image (AMI) ID to use for the instance. This particular AMI is a standard Ubuntu image.
    • The instance_type attribute specifies the type of instance to launch, in this case, a t2.micro, which is a low-cost, general-purpose instance.

ami

  1. Subnet and Security Group:

    • subnet_id assigns the instance to the public subnet we created earlier, ensuring it has network access.
    • vpc_security_group_ids associates the instance with the security group, allowing access on ports 22 (SSH), 8080 (HTTP), and 443 (HTTPS).
  2. Key Pair:

    • key_name specifies the SSH key pair used for secure access to the instance. This key pair was created earlier and allows you to log in to the instance.
  3. Public IP:

    • associate_public_ip_address = true ensures that the instance is assigned a public IP address, enabling external access.
  4. Dependency Management:

    • The depends_on attribute ensures that the instance creation waits until the security group and internet gateway are properly set up, ensuring the instance can properly communicate with the internet.
  5. User Data Script:

    • The user_data attribute contains a shell script that runs when the instance launches. It performs the following actions:
      • Updates the package lists with sudo apt update -y.
      • Installs NGINX with sudo apt install -y nginx.
      • Creates an index.html file in the NGINX web directory with a greeting message.
      • Configures NGINX to listen on port 8080 instead of the default port 80.
      • Restarts the NGINX service to apply the new configuration.
  6. Tags:

    • The tags attribute assigns a name to the instance, making it easier to identify and manage within the AWS Management Console.

This setup ensures that your instance is correctly configured to serve web traffic on port 8080 and is accessible with the necessary security configurations in place.

Create Output Variable for Ubuntu IP Address:

To display the public IP address of the newly created EC2 instance, use the following output configuration in Terraform:



output "ubuntu_instance_public_ip" {
  value = aws_instance.ubuntu_instance.public_ip
}


Enter fullscreen mode Exit fullscreen mode

This configuration will provide the public IP of your instance, allowing you to easily access it and verify that everything is set up correctly.

Initialize your Terraform Configuration:

Once you have your main.tf and other necessary files in place, the next step is to initialize your Terraform environment. Run the following command in your project directory:



terraform init


Enter fullscreen mode Exit fullscreen mode

terraform init

This command initializes the working directory containing your Terraform configuration files. It downloads the necessary provider plugins, sets up the backend, and prepares the environment for future Terraform operations.

Plan the Infrastructure Changes:

To preview the actions Terraform will take without actually applying any changes, run:



terraform plan


Enter fullscreen mode Exit fullscreen mode

terraform plan

This command generates an execution plan, which shows you what resources will be created, modified, or destroyed. It helps you ensure that everything is configured correctly before applying any changes.

Apply the Configuration:

Once we verify the planned changes. We will deploy the changes to AWS via the terraform apply command.

yes

Confirm the changes by typing “yes”.

Ubuntu EC2

Awesome! You just created your Ubuntu EC2 instance via Terraform.

Check through AWS UI:

Navigate to the AWS Management Console to verify your instance and other resources. You can now view the public IP address and other details directly in the console.

AWS UI

Access Through Port 8080 in Your Browser:

Open your web browser and enter http://<your-public-ip>:8080 in the address bar, replacing <your-public-ip> with the EC2 instance's public IP. You should see your "Hello From Ubuntu EC2 Instance!!!" message.

Port 8080

Destroy the Infrastructure:

If you want to tear down the infrastructure you created, use the terraform destroy command:



terraform destroy


Enter fullscreen mode Exit fullscreen mode

destroy-1

Confirm the changes by typing “yes”.

destroy-2

Delete all the resources defined in your configuration, ensuring a clean removal of everything Terraform created.

destroy-aws

Conclusion

In this guide, we have successfully utilized Terraform to automate the deployment of an AWS infrastructure, including a VPC, subnet, security group, and EC2 instance. By leveraging Terraform’s capabilities, we streamlined the process of provisioning and configuring resources, ensuring a consistent and efficient setup. Accessing the EC2 instance via port 8080 allows you to verify the deployment and confirm that your configuration is working as expected. This approach exemplifies the power of Infrastructure as Code (IaC) in managing cloud resources effectively and paves the way for scalable and manageable infrastructure solutions.

Stay tuned for our upcoming blogs where we’ll dive deeper into advanced Terraform concepts, including modules, remote backends, state management, and more. We’ll explore how these features can further enhance your infrastructure management and automation practices.

Top comments (6)

Collapse
 
bandook profile image
Brian Clarke

Hello,
Does the Ubuntu instance work for windows?

Collapse
 
sre_panchanan profile image
Panchanan Panigrahi

If Terraform and AWS CLI are properly installed and configured on a Windows machine, the EC2 instance should function as expected. The operating system of your local machine does not impact the performance of the EC2 instance, as the instance operates independently in the cloud. Since we are accessing the application on port 8080, there should be no issues related to your local OS. The key factors for successful access are ensuring that your security groups allow traffic on port 8080 and that the EC2 instance is correctly configured and running.

Collapse
 
bandook profile image
Brian Clarke

Great. Have been want to do this for sometime!

Thread Thread
 
sre_panchanan profile image
Panchanan Panigrahi

I'm glad to hear that! If you have any more questions or need further assistance, feel free to ask.

Collapse
 
bandook profile image
Brian Clarke

Panchanan:
Hello - With a few local hiccups, your terraform exercise worked like a charm,
I am balancing between terraform and FM/AI learning.
Thanks for the help!

Collapse
 
sre_panchanan profile image
Panchanan Panigrahi

I'm glad to hear it worked well for you despite the hiccups! Balancing Terraform and AI learning sounds exciting—keep up the great work!