Setting Up Terraform with Remote Backend and EC2 Instance Deployment: A Beginner's Guide
Table of contents
- Introduction
- What is Terraform?
- Key Features:
- Key Components of Terraform
- 1. Variables
- 2. Data Blocks
- 3. Outputs
- 4. State management
- 5. AWS S3 and DynamoDB
- Installing Terraform
- Output images (Ec2, S3,DynamoDB) :
Introduction
Infrastructure as Code (IaC) is a crucial skill for modern DevOps practitioners, and Terraform is one of the most popular tools in this space. With Terraform, you can automate the provisioning of your cloud infrastructure using simple, declarative configuration files.
In this blog, we’ll:
Understand the basics of Terraform and its building blocks.
Learn about AWS services like S3 and DynamoDB used for state management.
Implement a project to deploy an EC2 instance with Terraform, using a remote backend for state storage and locking.
What is Terraform?
Terraform is an open-source tool by HashiCorp that allows you to define, plan, and provision infrastructure in a consistent and predictable way. It supports multiple cloud providers like AWS, Azure, Google Cloud, and many more.
Key Features:
Infrastructure as Code (IaC): Write declarative configurations to define your infrastructure.
Idempotent: Terraform ensures the desired state is achieved, no matter how many times you apply it.
State Management: Tracks infrastructure changes through a state file, which can be stored remotely for collaboration.
Key Components of Terraform
1. Variables
Variables allow you to parameterize your Terraform configurations, making them reusable and dynamic.
Example:
variable "region" {
default = "us-east-1"
}
This variable sets the AWS region, which you can reuse across your Terraform files.
2. Data Blocks
Data blocks are used to fetch information from your infrastructure. For example, you can query existing resources or fetch AMI IDs.
Example:
data "aws_ami" "latest" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*"]
}
}
3. Outputs
Outputs allow you to display useful information after the infrastructure is provisioned, such as public IPs or resource IDs.
Example:
output "instance_public_ip" {
description = "The public IP of the created EC2 instance"
value = aws_instance.web.public_ip
}
4. State management
Terraform maintains the state of your infrastructure in a .tfstate
file. This file tracks the current state of resources and ensures consistency during updates.
State Locking: Prevents multiple users from modifying the state simultaneously.
Remote State: Stores the
.tfstate
file in a secure location like AWS S3 for better collaboration and versioning.
5. AWS S3 and DynamoDB
S3 (Simple Storage Service): We use S3 to store the Terraform state file, ensuring it's secure and accessible for collaboration.
DynamoDB: Acts as a locking mechanism to prevent multiple users from making concurrent changes to the state file.
Installing Terraform
Follow these steps to install Terraform.
1. Installing Terraform
To get started, you'll need to install Terraform on your system. Here's how you can do it:
For Windows:
Download the Terraform binary from the official Terraform website.
Extract the downloaded zip file and add its location to your system's PATH environment variable.
Open a terminal and run:
terraform --version
This verifies the installation.
For Ubuntu:
Update your package list:
sudo apt-get update
Install dependencies:
sudo apt-get install -y gnupg software-properties-common
Add HashiCorp's GPG key:
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
Add the HashiCorp repository:
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
Install Terraform:
sudo apt-get update && sudo apt-get install terraform
Verify the installation:
terraform --version
Project Overview
We’ll implement the following:
Set up a remote backend using S3 and DynamoDB for state storage and locking.
Deploy an EC2 instance using Terraform.
Prerequisites
Before you begin, make sure you have the following setup:
AWS Account: You should have an active AWS account with the necessary permissions to create resources like EC2, S3, and DynamoDB.
AWS CLI: Install and configure the AWS CLI with your AWS credentials to authenticate Terraform with your AWS account.
aws configure
Ensure your credentials are set up properly with access key and secret key.
EC2 Instance to Run Terraform: You need an EC2 instance with the necessary IAM permissions to create resources. Ensure the instance has:
IAM Role: Attach an IAM role with permissions to create S3 buckets, DynamoDB tables, and EC2 instances. Use a policy like
AmazonEC2FullAccess
,AmazonS3FullAccess
, andAmazonDynamoDBFullAccess
.SSH Access: You will need SSH access to the EC2 instance for running the Terraform commands.
Project Structure
Here’s the directory structure for our project:
TERRAFORM_PRACTICE/
├── Terraform-backend/
│ ├── backend_infra.tf
│ ├── main.tf
│ ├── terraform.tf
│ ├── variable.tf
│ ├── terraform.tfstate
│ ├── terraform.tfstate.backup
├── Terraform-resources/
│ ├── ec2.tf
│ ├── main.tf
│ ├── output.tf
│ ├── variable.tf
│ ├── terraform.tfstate
│ ├── terraform.tfstate.backup
│ ├── terraform-key.pub
Step 1: Setting Up the Remote Backend
A remote backend ensures your Terraform state file is stored securely. This also enables collaboration and locks the state file during updates to avoid conflicts.
backend_infra.tf :
resource "aws_s3_bucket" "terraform_aws_s3_bucket" {
bucket = var.aws_s3_bucket_name
tags = {
Name = var.aws_s3_bucket_name
}
}
resource "aws_dynamodb_table" "terraform_aws_db" {
name = var.aws_dynamodb_table_name
billing_mode = var.aws_db_billing_mode
hash_key = var.aws_db_hashkey
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = var.aws_dynamodb_table_name
}
}
terraform.tf :
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
variable.tf :
# AWS Region Variable
variable "aws_region" {
description = "The AWS region where resources are deployed (e.g., us-east-1)."
type = string
default = "us-east-1"
}
# Remote Backend Variables
variable "aws_s3_bucket_name" {
description = "The name of the S3 bucket for storing Terraform state files securely."
type = string
default = "terraform-s3-bucket-devops"
}
variable "aws_dynamodb_table_name" {
description = "The name of the DynamoDB table used for state locking to ensure consistency and prevent race conditions."
type = string
default = "terraform-db"
}
variable "aws_db_billing_mode" {
description = "The billing mode for the DynamoDB table (e.g., PAY_PER_REQUEST or PROVISIONED)."
type = string
default = "PAY_PER_REQUEST"
}
variable "aws_db_hashkey" {
description = "The primary key attribute name used in the DynamoDB table for state locking (e.g., LockID)."
type = string
default = "LockID"
}
Run the following commands to initialize and apply the backend configuration:
Run one command at a time from below :
terraform init
terraform plan
terraform apply --auto-approve
note : - -auto-approve is for automatically giving permission to craete and destroy
- terraform init :
- terraform apply - -auto-approve :
Step 2: Deploying an EC2 Instance
With the remote backend in place, we can now deploy an EC2 instance.
ec2.tf :
# Fetch the AMI for the instance based on specified filters
data "aws_ami" "ubuntu" {
owners = [var.aws_ami_owners]
most_recent = true
filter {
name = "name"
values = [var.aws_instance_os_type]
}
filter {
name = "state"
values = ["available"]
}
}
# Create an SSH key pair
resource "aws_key_pair" "terraform_key" {
key_name = var.aws_key_pair_name
public_key = file(var.aws_key_pair_public_key)
}
# Get the default VPC for the region
resource "aws_default_vpc" "default" {}
# Create a security group
resource "aws_security_group" "terraform_sg" {
name = var.aws_sg_name
description = var.aws_sg_description
vpc_id = aws_default_vpc.default.id
ingress {
description = "Allow access to SSH port 22"
from_port = 22
to_port = 22
protocol = var.ssh_protocol
cidr_blocks = [var.ssh_cidr]
}
ingress {
description = "Allow access to HTTP port 80"
from_port = 80
to_port = 80
protocol = var.http_protocol
cidr_blocks = [var.http_cidr]
}
ingress {
description = "Allow access to HTTPS port 443"
from_port = 443
to_port = 443
protocol = var.https_protocol
cidr_blocks = [var.https_cidr]
}
egress {
description = "Allow all outgoing traffic"
from_port = 0
to_port = 0
protocol = var.outgoing_protocol
cidr_blocks = [var.outgoing_cidr]
}
tags = {
Name = var.aws_sg_name
}
}
# Create an EC2 instance
resource "aws_instance" "terraform_instance" {
ami = data.aws_ami.ubuntu.id
instance_type = var.aws_instance_type
key_name = aws_key_pair.terraform_key.key_name
security_groups = [aws_security_group.terraform_sg.name]
root_block_device {
volume_size = var.aws_instance_storage_size
volume_type = var.aws_instance_volume_type
}
tags = {
Name = var.aws_instance_name
}
}
output.tf :
output "instance_private_ip" {
value = aws_instance.terraform_instance.private_ip
description = "The private IP address of the main server instance."
}
output "instance_public_ip" {
value = aws_instance.terraform_instance.public_ip
description = "The public IP address of the main server instance."
}
variable.tf :
# aws variable
variable "aws_region" {
description = "The AWS region to deploy resources in."
type = string
default = "us-east-1"
}
# EC2 instance variables
variable "aws_instance_type" {
description = "Defines the type of EC2 instance to be created (e.g., t2.micro, t3.small)."
type = string
default = "t2.micro"
}
variable "aws_instance_name" {
description = "Specifies the name tag for the EC2 instance, providing easy identification in AWS."
type = string
default = "terraform-server"
}
variable "aws_instance_os_type" {
description = "Defines the operating system image filter for selecting an appropriate AMI (e.g., Ubuntu 20.04)."
type = string
default = "ubuntu/images/hvm-ssd/*amd64*"
}
variable "aws_ami_owners" {
description = "The owner ID of the AMI to use. Default is Canonical (for Ubuntu AMIs)."
type = string
default = "099720109477"
}
variable "aws_instance_storage_size" {
description = "Specifies the size of the root block storage for the EC2 instance, in GB."
type = number
default = 10
}
variable "aws_instance_volume_type" {
description = "Defines the volume type for the EC2 instance (e.g., gp2, gp3, io1)."
type = string
default = "gp3"
}
# Key pair variables
variable "aws_key_pair_name" {
description = "The name of the SSH key pair to use for accessing the EC2 instance."
type = string
default = "~/terraform-key"
}
variable "aws_key_pair_public_key" {
description = "The path to the public key file for the SSH key pair."
type = string
default = "terraform-key.pub"
}
# Security group variables
variable "aws_sg_name" {
description = "Defines the name of the AWS Security Group to be created."
type = string
default = "my_terraform_sg"
}
variable "aws_sg_description" {
description = "Describes the purpose of the AWS Security Group being created."
type = string
default = "This security group allows SSH, HTTP, and HTTPS traffic."
}
variable "ssh_protocol" {
description = "Specifies the protocol to use for SSH traffic (default: TCP)."
type = string
default = "tcp"
}
variable "http_protocol" {
description = "Specifies the protocol to use for HTTP traffic (default: TCP)."
type = string
default = "tcp"
}
variable "https_protocol" {
description = "Specifies the protocol to use for HTTPS traffic (default: TCP)."
type = string
default = "tcp"
}
variable "ssh_cidr" {
description = "Defines the CIDR block to allow SSH access (default: open to all)."
type = string
default = "0.0.0.0/0"
}
variable "http_cidr" {
description = "Defines the CIDR block to allow HTTP access (default: open to all)."
type = string
default = "0.0.0.0/0"
}
variable "https_cidr" {
description = "Defines the CIDR block to allow HTTPS access (default: open to all)."
type = string
default = "0.0.0.0/0"
}
variable "outgoing_protocol" {
description = "Specifies the protocol for all outgoing traffic (default: allow all)."
type = string
default = "-1"
}
variable "outgoing_cidr" {
description = "Defines the CIDR block for all outgoing traffic (default: open to all)."
type = string
default = "0.0.0.0/0"
}
Run the following commands to deploy the EC2 instance:
Run one command at a time from below :
terraform init
terraform plan
terraform apply --auto-approve
terraform init :
terraform plan :
terraform apply - -auto-approve :
Step 3: Full Terraform Code
For Terraform full code use this github repo : https://github.com/Amitabh-DevOps/Terraform-for-devops-my
Output images (Ec2, S3,DynamoDB) :
Ec2 created :
S3 created :
DynamoDB created :
Conclusion
In this blog, you’ve learned:
What Terraform is and its core concepts.
How to set up a remote backend using AWS S3 and DynamoDB.
How to deploy an EC2 instance using Terraform.
This foundational project demonstrates the power of IaC and Terraform. As you progress, you can extend this setup to include more complex infrastructure components like load balancers, databases, and networking configurations.
Feel free to use this project, modify it, and share your thoughts in the comments!