Cloud Computing
Deploy real infrastructure on AWS Free Tier with Terraform. Includes EC2, S3, RDS, Lambda, and DynamoDB examples — all within free tier limits. No charges if you follow this guide.
LLuca Berton3 min read
On this page
- What You'll Build (For Free)
- Prerequisites
- AWS Free Tier Limits (2026)
- Project Setup
- Provider Configuration
- Variables
- Deploy EC2 Instance (Free Tier)
- Deploy S3 Bucket (Free Tier)
- Deploy DynamoDB Table (Always Free)
- Deploy Lambda Function (Always Free)
- Deploy RDS Instance (Free Tier)
- Outputs
- Deploy Everything
- ⚠️ Avoiding Unexpected Charges
- What's NOT Free (Common Mistakes)
- Clean Up
- Next Steps
- Hands-On Courses
- Conclusion
What You'll Build (For Free)
This guide shows you how to use Terraform with the AWS Free Tier to deploy real infrastructure without spending a cent. You'll provision:
- EC2 instance (t2.micro/t3.micro — 750 hours/month free)
- S3 bucket (5 GB storage free)
- DynamoDB table (25 GB storage, 25 read/write capacity units free)
- Lambda function (1M requests/month free)
- RDS instance (db.t3.micro — 750 hours/month free, 20 GB storage)
Important: AWS Free Tier lasts 12 months from account creation for most services. Always-Free services (Lambda, DynamoDB) remain free indefinitely.
Prerequisites
- AWS account — sign up at aws.amazon.com
- Terraform installed — Install Terraform on Mac or Ubuntu
- AWS CLI configured — Install AWS CLI
AWS Free Tier Limits (2026)
| Service | Free Tier Allowance | Duration |
|---|---|---|
| EC2 (t2.micro/t3.micro) | 750 hours/month | 12 months |
| S3 | 5 GB storage, 20,000 GET, 2,000 PUT | 12 months |
| RDS (db.t3.micro) | 750 hours/month, 20 GB | 12 months |
| DynamoDB | 25 GB, 25 RCU/WCU | Always free |
| Lambda | 1M requests, 400,000 GB-seconds | Always free |
| CloudWatch | 10 custom metrics, 10 alarms | Always free |
| SNS | 1M publishes | Always free |
| SQS | 1M requests | Always free |
| API Gateway | 1M REST API calls/month | 12 months |
Project Setup
Create your project structure:
mkdir terraform-aws-free-tier && cd terraform-aws-free-tierProvider Configuration
Create providers.tf:
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "terraform-free-tier"
Environment = "learning"
ManagedBy = "terraform"
}
}
}Variables
Create variables.tf:
variable "aws_region" {
description = "AWS region for resources"
type = string
default = "us-east-1" # Most free tier resources available here
}
variable "project_name" {
description = "Project name prefix for resource naming"
type = string
default = "free-tier-lab"
}Deploy EC2 Instance (Free Tier)
Create ec2.tf:
# Get the latest Amazon Linux 2023 AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# Security group allowing SSH
resource "aws_security_group" "web" {
name_prefix = "${var.project_name}-web-"
description = "Allow SSH and HTTP"
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Restrict to your IP in production
}
ingress {
description = "HTTP"
from_port = 80
to_port = 80
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 = "${var.project_name}-web-sg"
}
}
# EC2 instance — t2.micro is free tier eligible
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro" # FREE TIER: 750 hours/month
vpc_security_group_ids = [aws_security_group.web.id]
root_block_device {
volume_size = 8 # FREE TIER: up to 30 GB EBS
volume_type = "gp3"
encrypted = true
}
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Terraform on AWS Free Tier!</h1>" > /var/www/html/index.html
EOF
tags = {
Name = "${var.project_name}-web-server"
}
}Deploy S3 Bucket (Free Tier)
Create s3.tf:
resource "aws_s3_bucket" "data" {
bucket_prefix = "${var.project_name}-data-"
tags = {
Name = "${var.project_name}-data"
}
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256" # Free — no KMS charges
}
}
}
# Block all public access (security best practice)
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}Deploy DynamoDB Table (Always Free)
Create dynamodb.tf:
resource "aws_dynamodb_table" "app" {
name = "${var.project_name}-sessions"
billing_mode = "PROVISIONED"
# FREE TIER: 25 RCU + 25 WCU
read_capacity = 5
write_capacity = 5
hash_key = "session_id"
range_key = "timestamp"
attribute {
name = "session_id"
type = "S"
}
attribute {
name = "timestamp"
type = "N"
}
ttl {
attribute_name = "expires_at"
enabled = true
}
tags = {
Name = "${var.project_name}-sessions"
}
}Deploy Lambda Function (Always Free)
Create lambda.tf:
data "archive_file" "lambda" {
type = "zip"
output_path = "${path.module}/lambda.zip"
source {
content = <<-EOF
exports.handler = async (event) => {
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: "Hello from Terraform + Lambda!",
timestamp: new Date().toISOString(),
event: event
})
};
};
EOF
filename = "index.js"
}
}
resource "aws_iam_role" "lambda" {
name_prefix = "${var.project_name}-lambda-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "api" {
filename = data.archive_file.lambda.output_path
source_code_hash = data.archive_file.lambda.output_base64sha256
function_name = "${var.project_name}-api"
role = aws_iam_role.lambda.arn
handler = "index.handler"
runtime = "nodejs20.x"
memory_size = 128 # Minimum — saves free tier quota
timeout = 10
tags = {
Name = "${var.project_name}-api"
}
}
# Function URL (free alternative to API Gateway)
resource "aws_lambda_function_url" "api" {
function_name = aws_lambda_function.api.function_name
authorization_type = "NONE"
}Deploy RDS Instance (Free Tier)
Create rds.tf:
resource "aws_db_instance" "postgres" {
identifier_prefix = "${var.project_name}-"
engine = "postgres"
engine_version = "16.4"
instance_class = "db.t3.micro" # FREE TIER: 750 hours/month
allocated_storage = 20 # FREE TIER: up to 20 GB
max_allocated_storage = 20 # Prevent auto-scaling beyond free tier
storage_type = "gp2"
storage_encrypted = true
db_name = "appdb"
username = "dbadmin"
password = var.db_password # Use terraform.tfvars or env var
skip_final_snapshot = true # OK for learning environments
publicly_accessible = false
backup_retention_period = 0 # Disable backups (saves storage costs)
tags = {
Name = "${var.project_name}-postgres"
}
}
variable "db_password" {
description = "Database master password"
type = string
sensitive = true
default = "ChangeMe123!" # Override with TF_VAR_db_password env var
}Outputs
Create outputs.tf:
output "ec2_public_ip" {
description = "EC2 instance public IP"
value = aws_instance.web.public_ip
}
output "s3_bucket_name" {
description = "S3 bucket name"
value = aws_s3_bucket.data.id
}
output "dynamodb_table_name" {
description = "DynamoDB table name"
value = aws_dynamodb_table.app.name
}
output "lambda_url" {
description = "Lambda function URL"
value = aws_lambda_function_url.api.function_url
}
output "rds_endpoint" {
description = "RDS endpoint"
value = aws_db_instance.postgres.endpoint
}
output "estimated_monthly_cost" {
description = "Estimated cost"
value = "$0.00 (within AWS Free Tier limits)"
}Deploy Everything
# Initialize Terraform
terraform init
# Preview what will be created
terraform plan
# Deploy (type 'yes' when prompted)
terraform apply
# Test the Lambda function
curl $(terraform output -raw lambda_url)⚠️ Avoiding Unexpected Charges
Follow these rules to stay within the free tier:
- Don't run multiple t2.micro instances — you only get 750 hours total (1 instance × 24h × 31 days = 744 hours)
- Stay in one region — free tier limits are global, not per-region
- Set billing alerts:
resource "aws_budgets_budget" "free_tier" {
name = "free-tier-alert"
budget_type = "COST"
limit_amount = "1"
limit_unit = "USD"
time_unit = "MONTHLY"
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
subscriber_email_addresses = ["your-email@example.com"]
}
}- Destroy resources when done:
terraform destroy- Check the AWS Free Tier Usage dashboard at
console.aws.amazon.com/billing/home#/freetier
What's NOT Free (Common Mistakes)
| Resource | Why It Costs Money |
|---|---|
| NAT Gateway | ~$32/month — use public subnets for learning |
| Elastic IP (unattached) | $3.60/month per unused EIP |
| EBS snapshots beyond 1 GB | $0.05/GB/month |
| Data transfer out > 100 GB | $0.09/GB |
| t2.micro in multiple regions | 750h is global, not per-region |
| RDS Multi-AZ | Not free tier eligible |
Clean Up
Always destroy your lab resources when you're done learning:
terraform destroy -auto-approveVerify in the AWS Console that all resources are terminated.
Next Steps
- Getting Started with Terraform on AWS
- Terraform AWS VPC Complete Example
- Terraform Variables Complete Guide
- Terraform Backend with S3 and DynamoDB
Hands-On Courses
Conclusion
The AWS Free Tier gives you real infrastructure to practice Terraform — EC2, S3, RDS, Lambda, and DynamoDB all at zero cost for 12 months. The key is staying within limits: one t2.micro, one db.t3.micro, and careful monitoring with billing alerts. Use terraform destroy when you're done, and you'll never see an unexpected bill.
#AWS#Terraform#Free Tier#Infrastructure as Code#Beginners
AWS EventBridge Rules and Targets with Terraform
Build event-driven architectures with AWS EventBridge managed by Terraform — custom buses, rules, and cross-account events.
2 min read
AWS Step Functions Workflows with Terraform
Orchestrate serverless workflows with AWS Step Functions and Terraform — state machines, error handling, and retries. Step-by-step guide with code examples a...
2 min read
AWS WAF Web Application Firewall with Terraform
Protect your applications with AWS WAF rules managed by Terraform — rate limiting, IP blocking, and SQL injection prevention.
2 min read
AWS Secrets Manager Integration with Terraform
Manage secrets securely with AWS Secrets Manager and Terraform — rotation, replication, and application integration. Step-by-step guide with code examples an...
2 min read

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.