Table of Contents
ToggleIntroduction.
In today’s cloud-native world, automation is no longer a luxury—it’s a necessity. Managing cloud infrastructure manually through the AWS Console can be error-prone, time-consuming, and hard to reproduce.
That’s where Infrastructure as Code (IaC) tools like Terraform come into play. With Terraform, you can define your entire cloud architecture using simple configuration files, enabling you to deploy and manage infrastructure in a reliable, repeatable, and scalable way. One of the most common cloud architectures in AWS involves provisioning compute and database resources—specifically EC2 (Elastic Compute Cloud) for running applications and RDS (Relational Database Service) for managing relational databases such as MySQL or PostgreSQL.
This blog post walks you through a practical example: deploying both an EC2 instance and an RDS database instance using Terraform and securely connecting the two.
We’ll cover the essential components of a typical infrastructure setup, including networking (VPC, subnets, and security groups), provisioning of the compute and database resources, and configuring the EC2 instance to communicate with the RDS instance. By the end, you’ll have a working setup that mirrors what you’d deploy in a real-world application environment.
This guide is beginner-friendly but assumes a basic understanding of AWS services and Terraform syntax. You’ll learn how to structure Terraform files, use variables for flexibility, and implement security best practices such as restricting inbound traffic using security groups. We’ll also highlight important configuration steps, like ensuring that the RDS instance is only accessible to the EC2 instance and not exposed publicly, unless explicitly required.
The process starts by creating a custom VPC to isolate your resources, followed by provisioning subnets that allow public access for EC2 and private connectivity for RDS.
Next, we’ll configure route tables and internet gateways to manage traffic, define security groups to handle firewall rules, and then move on to resource provisioning. With Terraform’s declarative approach, you’ll see how multiple AWS services can be coordinated with just a few lines of configuration code.
Additionally, we’ll include Terraform output variables to display the EC2 public IP and RDS endpoint upon deployment, which makes it easier to test the connection. We’ll also use EC2 user data scripts to install the MySQL client automatically during instance launch.
This automation ensures that the EC2 instance is immediately ready to connect to the RDS instance once it’s up and running.
Whether you’re a DevOps engineer, cloud developer, or simply exploring Terraform for personal projects, this walkthrough will equip you with a solid foundation for deploying connected resources in AWS. By the end of this tutorial, you’ll not only have a functional EC2-RDS setup but also the skills to extend and manage more complex infrastructures in the future. Let’s dive in and bring your AWS environment to life—declaratively, repeatably, and efficiently—with Terraform.
Prerequisites
- Terraform installed
- AWS CLI configured
- IAM user with necessary permissions
- Key pair for SSH (if connecting to EC2 manually)
1. Main.tf
provider "aws" {
region = var.aws_region
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "subnet" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table" "rt" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
}
resource "aws_route_table_association" "a" {
subnet_id = aws_subnet.subnet.id
route_table_id = aws_route_table.rt.id
}
resource "aws_security_group" "ec2_sg" {
name = "ec2_sg"
description = "Allow SSH and DB traffic"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.rds_sg.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "rds_sg" {
name = "rds_sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.ec2_sg.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "ec2" {
ami = var.ec2_ami
instance_type = "t2.micro"
subnet_id = aws_subnet.subnet.id
security_groups = [aws_security_group.ec2_sg.name]
key_name = var.key_name
tags = {
Name = "Terraform-EC2"
}
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y mysql
EOF
}
resource "aws_db_instance" "rds" {
identifier = "terraform-mysql-db"
allocated_storage = 20
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
name = var.db_name
username = var.db_username
password = var.db_password
skip_final_snapshot = true
publicly_accessible = true
vpc_security_group_ids = [aws_security_group.rds_sg.id]
db_subnet_group_name = aws_db_subnet_group.default.name
}
resource "aws_db_subnet_group" "default" {
name = "rds-subnet-group"
subnet_ids = [aws_subnet.subnet.id]
}

2.Variables.tf
variable "aws_region" {
default = "us-east-1"
}
variable "ec2_ami" {
default = "ami-0c02fb55956c7d316" # Amazon Linux 2 AMI (replace with your region's ID)
}
variable "key_name" {
description = "SSH key name"
}
variable "db_name" {
default = "mydatabase"
}
variable "db_username" {
default = "admin"
}
variable "db_password" {
description = "DB password"
sensitive = true
}

3. terraform.tfvars
key_name = "your-ssh-key-name"
db_password = "YourSecurePassword123"

4. Outputs.tf
output "ec2_public_ip" {
value = aws_instance.ec2.public_ip
}
output "rds_endpoint" {
value = aws_db_instance.rds.endpoint
}

Deploy with Terraform
terraform init
terraform plan
terraform apply




Connect to RDS from EC2
Once applied:
SSH into your EC2 instance:
ssh -i your-key.pem ec2-user@<ec2_public_ip>
Connect to RDS:
mysql -h <rds_endpoint> -u admin -p
Conclusion.
In this guide, we explored how to launch an EC2 instance and an RDS database instance in AWS using Terraform, and how to connect them securely and efficiently. By leveraging the power of Infrastructure as Code (IaC), we eliminated manual provisioning, reduced configuration errors, and made our deployment process reproducible and scalable.
We started with the fundamentals—setting up a custom VPC, creating subnets, configuring security groups, and ensuring our route tables and internet gateways were properly established. These components laid the networking foundation necessary for secure and efficient communication between the EC2 and RDS instances.
Next, we provisioned the EC2 instance and RDS MySQL instance using Terraform’s declarative syntax. We ensured that the EC2 instance had the necessary access to communicate with the database through properly scoped security groups, avoiding any unnecessary public exposure of the RDS instance. This step emphasized the importance of cloud security best practices and the principle of least privilege.
We also implemented user data scripts to automate the installation of the MySQL client on the EC2 instance, demonstrating how Terraform can be used not only to provision infrastructure but also to bootstrap application environments. The final output included crucial connection details such as the EC2 public IP and the RDS endpoint, making it easy to test connectivity and validate the setup.
By following this tutorial, you’ve gained hands-on experience with one of the most common and practical use cases in AWS—connecting a compute instance to a managed database. You now understand how to break down a multi-resource infrastructure into reusable, modular Terraform code and deploy it with minimal effort.
More importantly, you’ve learned how to think declaratively about infrastructure. Rather than clicking through the AWS Console, you can now express your infrastructure in code, store it in version control, review changes through pull requests, and roll back configurations when needed. This is the power of Terraform and Infrastructure as Code.
As you move forward, consider expanding this setup. You could introduce private subnets, use NAT gateways for more secure communication, or integrate Secrets Manager to handle database credentials more securely. You could also modularize the Terraform configuration to make your codebase more maintainable and reusable across environments.
In a world where speed, scalability, and security are critical, Terraform helps you bring all three to your cloud infrastructure. With the skills and knowledge you’ve acquired, you’re well on your way to building more sophisticated and production-ready AWS environments.
Thank you for following along. Happy provisioning—and may your infrastructure always be version-controlled and error-free!