Skip to main content

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.
1

Add the module source

Create a main.tf file and declare the module:
main.tf
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.
2

Configure required variables

Every module call requires identifier. The following variables are needed for a functional instance:
VariableDescriptionExample
identifierUnique name for the RDS instance"demodb"
engineDatabase engine"mysql"
engine_versionEngine version"8.0"
instance_classEC2 instance type"db.t3a.large"
allocated_storageInitial storage in GiB5
usernameMaster 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.
3

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.
4

Configure the parameter group

Set family to the parameter group family that matches your engine and version:
Engine + versionfamily
MySQL 8.0mysql8.0
MySQL 8.4mysql8.4
PostgreSQL 17postgres17
PostgreSQL 16postgres16
MariaDB 10.11mariadb10.11
Oracle SE2 19oracle-se2-19
SQL Server SE 15sqlserver-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.
5

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.
main.tf
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.
main.tf
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:
outputs.tf
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.

Build docs developers (and LLMs) love