Provision Your First AWS Infrastructure with Terraform
Learn Terraform from scratch by provisioning an S3 bucket, EC2 instance, and security group on AWS using infrastructure as code with state management.
Domains & Technologies
Blueprint Walkthrough
Architecture Overview
This project introduces Terraform — the most widely used Infrastructure as Code tool in the DevOps industry. Instead of clicking through the AWS console to create resources, you describe what you want in a .tf file, run one command, and Terraform creates everything automatically.
You will provision three real AWS resources: an S3 bucket for file storage, an EC2 instance to run a server, and a security group to control access. You will also learn how Terraform tracks what it has created using a state file — the mechanism that makes updates and deletions safe.
Your Terraform Code (.tf files) | v terraform plan(shows what will change) | v terraform apply(creates the resources) | +----+----+ | | v v S3 Bucket EC2 Instance (storage) (+ security group) | | +---------+ | terraform.tfstate(tracks what exists)Problem Solved
Manually creating AWS resources through the console causes two major problems. First, nobody can see exactly how the infrastructure was built — there is no record of which settings were chosen, which checkboxes were ticked, or why. When something breaks, reconstruction is guesswork.
Second, creating the same setup in a second environment (staging, production) means clicking through every screen again and hoping you match the original exactly. Mistakes creep in. Environments drift apart.
Terraform solves both problems. Your .tf files are the exact specification of your infrastructure — checked into Git, reviewed in pull requests, and applied identically across every environment.
Step-by-Step Implementation Guide
Step 1: Install Terraform and Configure AWS
## Install Terraform using tfenv (manages multiple versions)git clone https://github.com/tfutils/tfenv.git ~/.tfenvexport PATH="$HOME/.tfenv/bin:$PATH"echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bashrc tfenv install 1.7.0tfenv use 1.7.0terraform --version## Expected: Terraform v1.7.0 ## Configure AWS credentials## Create an IAM user in AWS console with programmatic access## Attach the AdministratorAccess policy (for learning only)aws configure## AWS Access Key ID: your-access-key## AWS Secret Access Key: your-secret-key## Default region name: ap-south-1## Default output format: json ## Verify AWS credentials workaws sts get-caller-identity## Expected: Shows your AWS account ID and IAM user nameSecurityFor this learning project, AdministratorAccess is acceptable. In a real job, always use the principle of least privilege — only grant the permissions Terraform actually needs. Create specific IAM policies for each use case.
Step 2: Understand the Terraform Workflow
Before writing any code, understand the three commands you will use constantly:
terraform init— Downloads the AWS provider plugin. Run once per new project.terraform plan— Shows what Terraform WILL DO without actually doing it. Always run this before apply.terraform apply— Actually creates, updates, or destroys resources. Always review the plan first.terraform destroy— Deletes everything Terraform created. Useful for cleanup.
## Create your project directorymkdir terraform-basics && cd terraform-basicsStep 3: Write Your First Terraform Configuration
Create main.tf:
## Tell Terraform which providers (cloud SDKs) to useterraform { required_version = ">= 1.7.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } }} ## Configure the AWS provider## This is how Terraform knows which region to useprovider "aws" { region = "ap-south-1" # Mumbai, India}Create variables.tf — this keeps your configuration flexible:
## Variables let you reuse the same code with different values## Think of them like function parameters variable "project_name" { description = "Name prefix for all resources we create" type = string default = "devops-learning"} variable "environment" { description = "Deployment environment" type = string default = "development" # Validation ensures only valid values are accepted validation { condition = contains( ["development", "staging", "production"], var.environment ) error_message = "Must be development, staging, or production." }} variable "my_ip" { description = "Your IP for SSH access (find at checkip.amazonaws.com)" type = string}Create outputs.tf — values Terraform prints after applying:
## Outputs are like the return value of your Terraform code## They print useful information after terraform apply completes output "s3_bucket_name" { description = "Name of the created S3 bucket" value = aws_s3_bucket.main.bucket} output "ec2_public_ip" { description = "Public IP of the EC2 instance" value = aws_instance.web.public_ip} output "ec2_instance_id" { description = "Instance ID for AWS console" value = aws_instance.web.id}Step 4: Create an S3 Bucket
Add to main.tf:
## Local values — computed values reused throughout the configlocals { common_tags = { Project = var.project_name Environment = var.environment ManagedBy = "terraform" Owner = "devops-network-student" }} ## S3 Bucket for file storage## Resource format: resource "TYPE" "LOCAL_NAME"## TYPE = AWS resource type## LOCAL_NAME = name used within Terraform code onlyresource "aws_s3_bucket" "main" { # Bucket names must be globally unique across ALL AWS accounts # Adding a random suffix ensures uniqueness bucket = "${var.project_name}-${var.environment}-files" tags = local.common_tags} ## Enable versioning — keeps old versions of files when overwrittenresource "aws_s3_bucket_versioning" "main" { bucket = aws_s3_bucket.main.id versioning_configuration { status = "Enabled" }} ## Block all public access — S3 buckets are private by default## This resource makes that explicit and prevents accidental exposureresource "aws_s3_bucket_public_access_block" "main" { bucket = aws_s3_bucket.main.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true} ## Enable server-side encryption — all files encrypted at restresource "aws_s3_bucket_server_side_encryption_configuration" "main" { bucket = aws_s3_bucket.main.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } }}Step 5: Create a Security Group and EC2 Instance
Add to main.tf:
## Security Group — controls which traffic can reach the EC2 instance## Like a virtual firewall specific to this instanceresource "aws_security_group" "web" { name = "${var.project_name}-web-sg" description = "Allow SSH from my IP and HTTP from anywhere" # Allow SSH only from your IP address ingress { description = "SSH from my IP only" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["${var.my_ip}/32"] } # Allow HTTP web traffic from anywhere ingress { description = "HTTP from internet" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # Allow all outbound traffic # (so the server can download packages, reach AWS APIs, etc.) egress { description = "All outbound traffic" from_port = 0 to_port = 0 protocol = "-1" # -1 means all protocols cidr_blocks = ["0.0.0.0/0"] } tags = local.common_tags} ## Find the latest Ubuntu 22.04 AMI automatically## This data source queries AWS for current AMI IDs## Much better than hardcoding an AMI ID that may become outdateddata "aws_ami" "ubuntu" { most_recent = true owners = ["099720109477"] # Canonical (Ubuntu's publisher) filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] } filter { name = "virtualization-type" values = ["hvm"] }} ## EC2 Instanceresource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = "t2.micro" # Free tier eligible vpc_security_group_ids = [aws_security_group.web.id] # User data runs when the instance first boots # This installs a basic web server automatically user_data = <<-EOF #!/bin/bash apt update -y apt install -y nginx systemctl start nginx systemctl enable nginx echo '<h1>Provisioned by Terraform!</h1>' \ > /var/www/html/index.html EOF tags = merge(local.common_tags, { Name = "${var.project_name}-web-server" })}Step 6: Initialise, Plan, and Apply
## Step 1: Initialise Terraform## Downloads the AWS provider plugin (only needed once per project)terraform init## Expected: Terraform has been successfully initialized! ## Step 2: Validate your configuration syntaxterraform validate## Expected: Success! The configuration is valid. ## Step 3: Create a terraform.tfvars file with your valuescat > terraform.tfvars << EOFproject_name = "devops-learning"environment = "development"my_ip = "$(curl -s https://checkip.amazonaws.com)"EOF ## Step 4: Preview what Terraform will createterraform plan## Expected: Plan: 7 to add, 0 to change, 0 to destroy## Read every line — understand what is being created ## Step 5: Apply the configurationterraform apply## Type 'yes' when prompted to confirm## Expected: Apply complete! Resources: 7 added. ## Step 6: See the outputsterraform output## Expected:## ec2_instance_id = "i-0abc123"## ec2_public_ip = "13.x.x.x"## s3_bucket_name = "devops-learning-development-files"TipAlways run
terraform planbeforeterraform apply. The plan output tells you exactly what will be created, changed, or destroyed. Never apply without reviewing the plan — this habit saves you from expensive mistakes.
Step 7: Explore State and Make a Change
The state file is how Terraform knows what already exists.
## View the state file (do not edit manually)cat terraform.tfstate## Expected: JSON file listing all created resources and their IDs ## List all resources in stateterraform state list## Expected:## aws_instance.web## aws_s3_bucket.main## aws_s3_bucket_versioning.main## aws_s3_bucket_public_access_block.main## aws_s3_bucket_server_side_encryption_configuration.main## aws_security_group.web## data.aws_ami.ubuntu ## Show details of a specific resourceterraform state show aws_instance.web## Expected: All attributes of the EC2 instance ## Make a change — add a tag to the EC2 instance## Edit main.tf, add 'Team = "platform"' to the instance tags## Then run plan to see the changeterraform plan## Expected: Plan: 0 to add, 1 to change, 0 to destroy## The ~ symbol means modify in place terraform apply## Terraform only changes the tag — everything else stays the sameStep 8: Clean Up
## Destroy all resources when done## This is important — EC2 instances cost money when runningterraform plan -destroy## Review what will be deleted terraform destroy## Type 'yes' to confirm## Expected: Destroy complete! Resources: 7 destroyed. ## Verify in AWS console that everything is goneaws ec2 describe-instances \ --filters "Name=tag:Project,Values=devops-learning" \ --query 'Reservations[].Instances[].State.Name'## Expected: ["terminated"]Validation & Testing
## 1. Verify S3 bucket was created with encryptionaws s3api get-bucket-encryption \ --bucket devops-learning-development-files## Expected: SSEAlgorithm = AES256 ## 2. Verify S3 bucket blocks public accessaws s3api get-public-access-block \ --bucket devops-learning-development-files## Expected: all four settings = true ## 3. Verify EC2 is running and Nginx worksEC2_IP=$(terraform output -raw ec2_public_ip)curl http://$EC2_IP## Expected: HTML with 'Provisioned by Terraform!' ## 4. Test idempotency (the most important Terraform property)## Run apply again — nothing should changeterraform apply## Expected: Apply complete! Resources: 0 added, 0 changed, 0 destroyed.## Running apply twice is safe — Terraform only changes what differs ## 5. Verify state file existsls -la terraform.tfstate## Expected: File exists with recent timestamp ## 6. Test changing a variable value## Change environment to 'staging' in terraform.tfvars## Run plan — Terraform will want to recreate resources## with the new name (staging instead of development)terraform plan## Expected: Several resources to be destroyed and created## (because the bucket name includes the environment)## This shows how Terraform handles configuration changes ## 7. Clean up before paying for EC2terraform destroy --auto-approveecho "Terraform project complete!"Videos & Guides
Terraform Tutorial for Beginners — TechWorld with Nana
Complete Terraform beginner tutorial covering infrastructure as code concepts, Terraform workflow, AWS provider, state files, variables, and outputs.
Terraform AWS Provider Documentation
Official Terraform AWS provider documentation — reference for every AWS resource and data source available in Terraform with examples.
HashiCorp Terraform Getting Started Guide
Official HashiCorp getting started tutorial for Terraform with AWS — covers installation, first configuration, plan and apply workflow, variables, and outputs.