AWS EventBridge Rules and Targets with Terraform
Build event-driven architectures with AWS EventBridge managed by Terraform — custom buses, rules, and cross-account events.
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.
This guide shows you how to use Terraform with the AWS Free Tier to deploy real infrastructure without spending a cent. You'll provision:
Important: AWS Free Tier lasts 12 months from account creation for most services. Always-Free services (Lambda, DynamoDB) remain free indefinitely.
| 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 |
Create your project structure:
mkdir terraform-aws-free-tier && cd terraform-aws-free-tierCreate 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"
}
}
}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"
}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"
}
}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
}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"
}
}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"
}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
}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)"
}# 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)Follow these rules to stay within the free tier:
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"]
}
}terraform destroyconsole.aws.amazon.com/billing/home#/freetier| 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 |
Always destroy your lab resources when you're done learning:
terraform destroy -auto-approveVerify in the AWS Console that all resources are terminated.
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.
Build event-driven architectures with AWS EventBridge managed by Terraform — custom buses, rules, and cross-account events.
Orchestrate serverless workflows with AWS Step Functions and Terraform — state machines, error handling, and retries. Step-by-step guide with code examples a...
Protect your applications with AWS WAF rules managed by Terraform — rate limiting, IP blocking, and SQL injection prevention.
Manage secrets securely with AWS Secrets Manager and Terraform — rotation, replication, and application integration. Step-by-step guide with code examples an...