Deploying Your First Server with Terraform: A Beginner's Guide
This is a simple walkthrough of how I deployed a basic web server on AWS using Terraform. The goal is not to overcomplicate things but to help you understand how the pieces fit together and get something running end to end.
Overview
In this project, we are:
- Creating a VPC
- Creating a public subnet
- Attaching an Internet Gateway
- Configuring a route table for internet access
- Setting up a security group (ports 22, 80, 443)
- Launching an EC2 instance
- Installing Apache using user data
- Accessing the server via public IP
At the end, you should be able to open your browser and see a simple HTML page served from your EC2 instance.
Prerequisites
Before you start, make sure you have:
- AWS CLI installed and configured (
aws configure) - Terraform installed (
terraform version) - VS Code installed
- Extensions:
- HashiCorp Terraform
- AWS Toolkit
Also ensure:
- You are using an IAM user, not root
- Your IAM user has enough permissions to create VPC, EC2, etc.
Step by Step Guide
1. Initialize your project
Create a folder and add a main.tf file. Paste your Terraform code into it.
You can as well just clone this repo, butit's advieable to always write it out manually, especially for beginners; to build muscle memory.
git clone https://github.com/Vivixell/Terraform-Aws-Webserver.git
cd Terraform-Aws-Webserver
2. Initialize Terraform
terraform init
This downloads the AWS provider and prepares your working directory.
3. Review the execution plan
terraform plan
This shows what Terraform is about to create. Nothing is deployed yet.
4. Apply the configuration
terraform apply
Type yes when prompted.
Terraform will now create all the resources defined in your code.
Terraform Code Breakdown
Terraform and Provider Block
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.9"
}
}
}
provider "aws" {
region = "us-east-1"
}
This defines:
Terraform version
AWS provider
Region to deploy resources
Terraform will use your AWS CLI credentials automatically.
VPC
resource "aws_vpc" "my_vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "my-vpc"
}
}
Creates a private network where all resources will live.
Subnet
resource "aws_subnet" "my_public_subnet" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
tags = {
Name = "my-public-subnet"
}
}
This subnet will host our EC2 instance.
Security Group
resource "aws_security_group" "my_web_sg" {
name = "web-sg"
vpc_id = aws_vpc.my_vpc.id
dynamic "ingress" {
for_each = [80, 443, 22]
content {
from_port = ingress.value
to_port = ingress.value
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"]
}
}
This allows:
SSH (22)
HTTP (80)
HTTPS (443)
The dynamic block is just a cleaner way to avoid repeating the same config three times. I stated the other way you can write it without using this method in the code.
Internet Gateway
resource "aws_internet_gateway" "my_igw" {
vpc_id = aws_vpc.my_vpc.id
tags = {
Name = "my-igw"
}
}
This enables internet access for your VPC.
Route Table
resource "aws_route_table" "my_public_rt" {
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 = "my-public-rt"
}
}
This routes internet traffic through the Internet Gateway.
Route Table Association
resource "aws_route_table_association" "my_public_rt_assoc" {
subnet_id = aws_subnet.my_public_subnet.id
route_table_id = aws_route_table.my_public_rt.id
}
Connects the subnet to the route table.
Key Pair
resource "aws_key_pair" "my_key_pair" {
key_name = "my-key-pair"
public_key = "your-public-key"
}
Used to SSH into the instance.
EC2 Instance
resource "aws_instance" "my_web_server" {
ami = "ami-0ec10929233384c7f"
instance_type = "t3.micro"
subnet_id = aws_subnet.my_public_subnet.id
vpc_security_group_ids = [aws_security_group.my_web_sg.id]
associate_public_ip_address = true
key_name = aws_key_pair.my_key_pair.key_name
user_data_replace_on_change = true
user_data = <<-EOF
#!/bin/bash
apt update -y
apt install -y apache2
systemctl start apache2
systemctl enable apache2
echo "<h1>Hello from Terraform Day3 By OVR </h1>" > /var/www/html/index.html
EOF
tags = {
Name = "my-web-server"
}
}
This:
Launches the instance
Installs Apache
Serves a simple HTML page
Output
output "Public_IP" {
value = aws_instance.my_web_server.public_ip
}
Prints the public IP after deployment.
Accessing Your Server
After terraform apply, copy the public IP and open:
http://<your-public-ip>
Do not use HTTPS since SSL is not configured.
Challenges I Faced
1. Instance type not eligible for free tier
Error:
InvalidParameterCombination: The specified instance type is not eligible for Free Tier
Fix:
Switched from t2.micro to t3.micro (or any free tier eligible type for you, it won't be a problem if you're not in free tier mode)
Tip:
Always confirm supported instance types in your region.
2. Could not access server from browser
Issue:
curl worked but browser didn’t
Fix:
Used http:// instead of https://
3. IAM permission issues (common)
If your IAM user lacks permissions, Terraform will fail.
Fix:
Attach appropriate policies like:
EC2FullAccess
VPCFullAccess (or scoped policies)
Possible Improvements
This setup works, but it can be better.
Use variables instead of hardcoding values
Split resources into modules
Use remote state (S3 + DynamoDB)
Use latest AMI lookup instead of hardcoding
Add proper domain and HTTPS
Final Thoughts
This is a basic but complete Terraform workflow:
- To verify setup run
terraform init
terraform plan
terraform apply
Don't forget to terraform destroy to clean up the infra.
Top comments (0)