Prerequisites
- Terraform
>= 1.11.1 installed (install guide)
- AWS credentials configured in your environment (
aws configure, environment variables, or an IAM role)
- An existing VPC with at least two subnets in different Availability Zones
- A security group that allows inbound traffic on the database port from your application
The module does not create a VPC or security group. Pass existing resource IDs via vpc_security_group_ids and either db_subnet_group_name (existing subnet group) or create_db_subnet_group = true + subnet_ids (create a new one).
Deploy a MySQL instance
The following example is taken directly from the module’s README.md. It creates a MySQL 8.0 instance with Enhanced Monitoring, IAM database authentication, and a custom parameter group.
Add the module source
Create a main.tf file and declare the module:module "db" {
source = "terraform-aws-modules/rds/aws"
identifier = "demodb"
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3a.large"
allocated_storage = 5
db_name = "demodb"
username = "user"
port = "3306"
iam_database_authentication_enabled = true
vpc_security_group_ids = ["sg-12345678"]
maintenance_window = "Mon:00:00-Mon:03:00"
backup_window = "03:00-06:00"
# Enhanced Monitoring
monitoring_interval = "30"
monitoring_role_name = "MyRDSMonitoringRole"
create_monitoring_role = true
tags = {
Owner = "user"
Environment = "dev"
}
# DB subnet group
create_db_subnet_group = true
subnet_ids = ["subnet-12345678", "subnet-87654321"]
# DB parameter group
family = "mysql8.0"
# DB option group
major_engine_version = "8.0"
# Deletion protection
deletion_protection = true
parameters = [
{
name = "character_set_client"
value = "utf8mb4"
},
{
name = "character_set_server"
value = "utf8mb4"
}
]
options = [
{
option_name = "MARIADB_AUDIT_PLUGIN"
option_settings = [
{
name = "SERVER_AUDIT_EVENTS"
value = "CONNECT"
},
{
name = "SERVER_AUDIT_FILE_ROTATIONS"
value = "37"
},
]
},
]
}
Replace sg-12345678 and the subnet_ids values with real IDs from your AWS account. Configure required variables
Every module call requires identifier. The following variables are needed for a functional instance:| Variable | Description | Example |
|---|
identifier | Unique name for the RDS instance | "demodb" |
engine | Database engine | "mysql" |
engine_version | Engine version | "8.0" |
instance_class | EC2 instance type | "db.t3a.large" |
allocated_storage | Initial storage in GiB | 5 |
username | Master DB username | "user" |
manage_master_user_password defaults to true, so you do not need to supply a password. RDS generates one and stores it in AWS Secrets Manager. Retrieve it after terraform apply using the db_instance_master_user_secret_arn output.
Configure networking
You must attach the instance to a VPC subnet group and one or more security groups:# Option A: create a new subnet group
create_db_subnet_group = true
subnet_ids = ["subnet-aaa", "subnet-bbb"]
# Option B: use an existing subnet group
create_db_subnet_group = false
db_subnet_group_name = "my-existing-subnet-group"
# Always required
vpc_security_group_ids = ["sg-12345678"]
Provide at least two subnets in different Availability Zones. This is required by RDS regardless of whether you enable Multi-AZ. Configure the parameter group
Set family to the parameter group family that matches your engine and version:| Engine + version | family |
|---|
| MySQL 8.0 | mysql8.0 |
| MySQL 8.4 | mysql8.4 |
| PostgreSQL 17 | postgres17 |
| PostgreSQL 16 | postgres16 |
| MariaDB 10.11 | mariadb10.11 |
| Oracle SE2 19 | oracle-se2-19 |
| SQL Server SE 15 | sqlserver-se-15.0 |
For MySQL and Oracle, also set major_engine_version so the module can create a matching option group:family = "mysql8.0"
major_engine_version = "8.0"
For PostgreSQL, the option group is never created (PostgreSQL does not support option groups), so major_engine_version is only informational. Initialize and apply
terraform init
terraform plan
terraform apply
The first apply typically takes 10–15 minutes. When it completes, retrieve the connection endpoint:terraform output db_instance_endpoint
Do not run terraform destroy on a production database without first setting deletion_protection = false and skip_final_snapshot = false. By default, the module creates a final snapshot and enables deletion protection, which prevents accidental data loss.
Complete MySQL example
This example is taken directly from examples/complete-mysql/. It creates a Multi-AZ MySQL 8.0 instance with Performance Insights, Enhanced Monitoring, CloudWatch log exports, and storage autoscaling. Supporting VPC and security group resources are created using community modules.
provider "aws" {
region = local.region
}
data "aws_availability_zones" "available" {}
locals {
name = "complete-mysql"
region = "eu-west-1"
vpc_cidr = "10.0.0.0/16"
azs = slice(data.aws_availability_zones.available.names, 0, 3)
tags = {
Name = local.name
Example = local.name
Repository = "https://github.com/terraform-aws-modules/terraform-aws-rds"
}
}
module "db" {
source = "terraform-aws-modules/rds/aws"
identifier = local.name
engine = "mysql"
engine_version = "8.0"
family = "mysql8.0" # DB parameter group
major_engine_version = "8.0" # DB option group
instance_class = "db.t4g.large"
allocated_storage = 20
max_allocated_storage = 100
db_name = "completeMysql"
username = "complete_mysql"
port = 3306
multi_az = true
db_subnet_group_name = module.vpc.database_subnet_group
vpc_security_group_ids = [module.security_group.security_group_id]
maintenance_window = "Mon:00:00-Mon:03:00"
backup_window = "03:00-06:00"
enabled_cloudwatch_logs_exports = ["general"]
create_cloudwatch_log_group = true
skip_final_snapshot = true
deletion_protection = false
performance_insights_enabled = true
performance_insights_retention_period = 7
create_monitoring_role = true
monitoring_interval = 60
parameters = [
{
name = "character_set_client"
value = "utf8mb4"
},
{
name = "character_set_server"
value = "utf8mb4"
}
]
tags = local.tags
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 6.0"
name = local.name
cidr = local.vpc_cidr
azs = local.azs
public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)]
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 3)]
database_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 6)]
create_database_subnet_group = true
tags = local.tags
}
module "security_group" {
source = "terraform-aws-modules/security-group/aws"
version = "~> 5.0"
name = local.name
description = "Complete MySQL example security group"
vpc_id = module.vpc.vpc_id
ingress_with_cidr_blocks = [
{
from_port = 3306
to_port = 3306
protocol = "tcp"
description = "MySQL access from within VPC"
cidr_blocks = module.vpc.vpc_cidr_block
},
]
tags = local.tags
}
Complete PostgreSQL example
This example is taken directly from examples/complete-postgres/. It creates a Multi-AZ PostgreSQL 17 instance with managed password rotation, Performance Insights, Enhanced Monitoring, and CloudWatch log exports. It also demonstrates automated backups replication to a second region.
provider "aws" {
region = local.region
}
data "aws_availability_zones" "available" {}
locals {
name = "complete-postgresql"
region = "eu-west-1"
vpc_cidr = "10.0.0.0/16"
azs = slice(data.aws_availability_zones.available.names, 0, 3)
tags = {
Name = local.name
Example = local.name
Repository = "https://github.com/terraform-aws-modules/terraform-aws-rds"
}
}
module "db" {
source = "terraform-aws-modules/rds/aws"
identifier = local.name
engine = "postgres"
engine_version = "17"
engine_lifecycle_support = "open-source-rds-extended-support-disabled"
family = "postgres17" # DB parameter group
major_engine_version = "17" # DB option group
instance_class = "db.t4g.large"
allocated_storage = 20
max_allocated_storage = 100
# NOTE: Do NOT use 'user' as the value for 'username' as it throws:
# "Error creating DB Instance: InvalidParameterValue: MasterUsername
# user cannot be used as it is a reserved word used by the engine"
db_name = "completePostgresql"
username = "complete_postgresql"
port = 5432
manage_master_user_password_rotation = true
master_user_password_rotate_immediately = false
master_user_password_rotation_schedule_expression = "rate(15 days)"
multi_az = true
db_subnet_group_name = module.vpc.database_subnet_group
vpc_security_group_ids = [module.security_group.security_group_id]
maintenance_window = "Mon:00:00-Mon:03:00"
backup_window = "03:00-06:00"
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
create_cloudwatch_log_group = true
backup_retention_period = 1
skip_final_snapshot = true
deletion_protection = false
performance_insights_enabled = true
performance_insights_retention_period = 7
create_monitoring_role = true
monitoring_interval = 60
monitoring_role_name = "example-monitoring-role-name"
monitoring_role_use_name_prefix = true
monitoring_role_description = "Description for monitoring role"
parameters = [
{
name = "autovacuum"
value = 1
},
{
name = "client_encoding"
value = "utf8"
}
]
tags = local.tags
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 6.0"
name = local.name
cidr = local.vpc_cidr
azs = local.azs
public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)]
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 3)]
database_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 6)]
create_database_subnet_group = true
tags = local.tags
}
module "security_group" {
source = "terraform-aws-modules/security-group/aws"
version = "~> 5.0"
name = local.name
description = "Complete PostgreSQL example security group"
vpc_id = module.vpc.vpc_id
ingress_with_cidr_blocks = [
{
from_port = 5432
to_port = 5432
protocol = "tcp"
description = "PostgreSQL access from within VPC"
cidr_blocks = module.vpc.vpc_cidr_block
},
]
tags = local.tags
}
PostgreSQL does not support option groups. When engine = "postgres" is set, the module automatically skips creating a db_option_group resource regardless of the create_db_option_group setting.
Common outputs
After terraform apply, reference these outputs in other modules or scripts:
output "db_instance_endpoint" {
description = "The connection endpoint (hostname:port)"
value = module.db.db_instance_endpoint
}
output "db_instance_address" {
description = "The hostname of the RDS instance"
value = module.db.db_instance_address
}
output "db_instance_arn" {
description = "The ARN of the RDS instance"
value = module.db.db_instance_arn
}
output "db_instance_master_user_secret_arn" {
description = "ARN of the Secrets Manager secret holding the master password"
value = module.db.db_instance_master_user_secret_arn
}
Retrieve the master password from Secrets Manager using the AWS CLI:
aws secretsmanager get-secret-value \
--secret-id "$(terraform output -raw db_instance_master_user_secret_arn)" \
--query SecretString \
--output text
Important operational notes
deletion_protection
Set deletion_protection = true on any instance you do not want accidentally deleted. Terraform will fail with an error if you attempt to destroy an instance with deletion protection enabled, which prevents mistakes. To delete the instance, first set deletion_protection = false and re-apply.
backup_window and maintenance_window
These two windows must not overlap. A common pattern taken from the examples above is:
maintenance_window = "Mon:00:00-Mon:03:00"
backup_window = "03:00-06:00"
Both values are in UTC. If you omit backup_window, AWS assigns a random window.
skip_final_snapshot
skip_final_snapshot defaults to false, meaning a final DB snapshot is created when you destroy the instance. Set it to true only in non-production environments where you do not need a recovery point.
For production, use both deletion_protection = true and skip_final_snapshot = false. For development environments where you frequently recreate infrastructure, use deletion_protection = false and skip_final_snapshot = true.