| name | aws-cloudformation-rds |
| description | AWS CloudFormation patterns for Amazon RDS databases. Use when creating RDS instances (MySQL, PostgreSQL, Aurora), DB clusters, multi-AZ deployments, parameter groups, subnet groups, and implementing template structure with Parameters, Outputs, Mappings, Conditions, and cross-stack references. |
| category | aws |
| tags | ["aws","cloudformation","rds","database","mysql","postgresql","aurora","mariadb","oracle","infrastructure","iaac"] |
| version | 1.0.0 |
| allowed-tools | Read, Write, Bash |
AWS CloudFormation RDS Database
Overview
Create production-ready Amazon RDS infrastructure using AWS CloudFormation templates. This skill covers RDS instances (MySQL, PostgreSQL, Aurora, MariaDB), DB clusters, multi-AZ deployments, parameter groups, subnet groups, security groups, template structure best practices, parameter patterns, and cross-stack references for modular, reusable infrastructure as code.
When to Use
Use this skill when:
- Creating new RDS database instances (MySQL, PostgreSQL, Aurora, MariaDB)
- Configuring DB clusters with read replicas
- Setting up multi-AZ deployments for high availability
- Creating DB parameter groups and option groups
- Configuring DB subnet groups for VPC deployment
- Implementing template Parameters with AWS-specific types
- Creating Outputs for cross-stack references
- Organizing templates with Mappings and Conditions
- Designing reusable, modular CloudFormation templates
- Integrating with Secrets Manager for credential management
Quick Start
Basic MySQL RDS Instance
AWSTemplateFormatVersion: 2010-09-09
Description: Simple MySQL RDS instance with basic configuration
Parameters:
DBInstanceIdentifier:
Type: String
Default: mydatabase
Description: Database instance identifier
MasterUsername:
Type: String
Default: admin
Description: Master username
MasterUserPassword:
Type: String
NoEcho: true
Description: Master user password
DBInstanceClass:
Type: String
Default: db.t3.micro
AllowedValues:
- db.t3.micro
- db.t3.small
- db.t3.medium
Resources:
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription:
Aurora MySQL Cluster
AWSTemplateFormatVersion: 2010-09-09
Description: Aurora MySQL cluster with writer and reader instances
Parameters:
DBClusterIdentifier:
Type: String
Default: my-aurora-cluster
Description: Cluster identifier
MasterUsername:
Type: String
Default: admin
MasterUserPassword:
Type: String
NoEcho: true
Resources:
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for Aurora
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
DBCluster:
Type: AWS::RDS::DBCluster
Properties:
DBClusterIdentifier: !Ref DBClusterIdentifier
Template Structure
Template Sections Overview
AWS CloudFormation templates are JSON or YAML files with specific sections. Each section serves a purpose in defining your infrastructure.
AWSTemplateFormatVersion: 2010-09-09
Description: Optional description string
Mappings: {}
Metadata: {}
Parameters: {}
Rules: {}
Conditions: {}
Transform: {}
Resources: {}
Outputs: {}
Format Version
The AWSTemplateFormatVersion identifies the template version. Current version is 2010-09-09.
AWSTemplateFormatVersion: 2010-09-09
Description: My RDS Database Template
Description
Add a description to document the template's purpose. Must appear after the format version.
AWSTemplateFormatVersion: 2010-09-09
Description: >
This template creates an RDS MySQL instance with:
- Multi-AZ deployment for high availability
- Encrypted storage
- Automated backups
- Performance Insights enabled
Metadata
Use Metadata for additional information about resources or parameters, including AWS::CloudFormation::Interface for parameter grouping.
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Database Configuration
Parameters:
- DBInstanceIdentifier
- Engine
- DBInstanceClass
- Label:
default: Credentials
Parameters:
- MasterUsername
- MasterUserPassword
- Label:
default: Network
Parameters:
- DBSubnetGroupName
- VPCSecurityGroups
ParameterLabels:
DBInstanceIdentifier:
default: Database Instance ID
MasterUsername:
default: Master Username
Resources Section
The Resources section is the only required section. It defines AWS resources to provision.
Resources:
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for RDS deployment
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
DBParameterGroup:
Type: AWS::RDS::DBParameterGroup
Properties:
Description: Custom parameter group for MySQL
Family: mysql8.0
Parameters:
max_connections: 200
innodb_buffer_pool_size: 1073741824
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: mydbinstance
DBInstanceClass: db.t3.micro
Engine: mysql
MasterUsername: admin
MasterUserPassword: !Ref
Parameters
Parameter Types
Use AWS-specific parameter types for validation and easier selection in the console.
Parameters:
DBInstanceIdentifier:
Type: String
Description: Database instance identifier
DBInstanceClass:
Type: AWS::RDS::DBInstance::InstanceType
Description: RDS instance class
Default: db.t3.micro
EngineVersion:
Type: AWS::RDS::DBInstance::Version
Description: Database engine version
Default: 8.0
VPCSecurityGroups:
Type: List<AWS::EC2::SecurityGroup::Id>
Description: Security groups for RDS instance
AWS::RDS::DBInstance::InstanceType Values
Common RDS instance types:
Parameters:
DBInstanceClass:
Type: String
AllowedValues:
- db.t3.micro
- db.t3.small
- db.t3.medium
- db.t3.large
- db.t3.xlarge
- db.t3.2xlarge
- db.m5.large
- db.m5.xlarge
- db.m5.2xlarge
- db.m5.4xlarge
- db.r5.large
- db.r5.xlarge
- db.r5.2xlarge
Parameter Constraints
Add constraints to validate parameter values.
Parameters:
DBInstanceIdentifier:
Type: String
Description: Database instance identifier
Default: mydatabase
AllowedPattern: "^[a-zA-Z][a-zA-Z0-9]*$"
ConstraintDescription: Must begin with a letter; contain only alphanumeric characters
MinLength: 1
MaxLength: 63
MasterUsername:
Type: String
Description: Master username
Default: admin
AllowedPattern: "^[a-zA-Z][a-zA-Z0-9]*$"
MinLength: 1
MaxLength: 16
NoEcho: true
MasterUserPassword:
Type: String
Description: Master user password
NoEcho: true
MinLength: 8
MaxLength: 41
Engine and Version Parameters
Parameters:
Engine:
Type: String
Description: Database engine
Default: mysql
AllowedValues:
- mysql
- postgres
- oracle-ee
- oracle-se2
- sqlserver-ee
- sqlserver-se
- sqlserver-ex
- sqlserver-web
- aurora
- aurora-mysql
- aurora-postgresql
- mariadb
EngineVersion:
Type: String
Description: Database engine version
Default: 8.0.35
DBFamily:
Type: String
Description: Parameter group family
Default: mysql8.0
AllowedValues:
- mysql5.6
SSM Parameter Types
Reference Systems Manager parameters for dynamic values.
Parameters:
LatestMySQLVersion:
Type: AWS::SSM::Parameter::Value<String>
Description: Latest MySQL version from SSM
Default: /rds/mysql/latest/version
LatestPostgreSQLVersion:
Type: AWS::SSM::Parameter::Value<String>
Description: Latest PostgreSQL version from SSM
Default: /rds/postgres/latest/version
NoEcho for Sensitive Data
Use NoEcho for passwords and sensitive values to mask them in console output.
Parameters:
MasterUserPassword:
Type: String
Description: Master user password
NoEcho: true
MinLength: 8
MaxLength: 41
Mappings
Use Mappings for static configuration data based on regions or instance types.
Mappings:
InstanceTypeConfig:
db.t3.micro:
CPU: 2
MemoryGiB: 1
StorageGB: 20
db.t3.small:
CPU: 2
MemoryGiB: 2
StorageGB: 20
db.t3.medium:
CPU: 2
MemoryGiB: 4
StorageGB: 20
db.m5.large:
CPU: 2
MemoryGiB: 8
StorageGB: 100
RegionDatabasePort:
us-east-1:
MySQL: 3306
PostgreSQL: 5432
us-west-2:
MySQL: 3306
PostgreSQL: 5432
eu-west-1:
MySQL: 3306
PostgreSQL: 5432
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: !FindInMap [InstanceTypeConfig, !Ref , ]
Conditions
Use Conditions to conditionally create resources based on parameters.
Parameters:
EnableMultiAZ:
Type: String
Default: false
AllowedValues:
- true
- false
EnableEncryption:
Type: String
Default: true
AllowedValues:
- true
- false
Environment:
Type: String
Default: development
AllowedValues:
- development
- staging
- production
Conditions:
IsMultiAZ: !Equals [!Ref EnableMultiAZ, true]
IsEncrypted: !Equals [!Ref EnableEncryption, true]
IsProduction: !Equals [!Ref Environment, production]
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
MultiAZ:
[, , ]
[, , ]
Condition Functions
Conditions:
IsDev: !Equals [!Ref Environment, development]
IsStaging: !Equals [!Ref Environment, staging]
IsProduction: !Equals [!Ref Environment, production]
HasLicense: !Not [!Condition IsDev]
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
LicenseModel: !If [HasLicense, "license-included", "bring-your-own-license"]
StorageType: !If [IsProduction, "io1", "gp3"]
Iops: !If [IsProduction, 3000, !Ref AWS::NoValue]
Transform
Use Transform for macros like AWS::Serverless for SAM templates.
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Serverless RDS application template
Globals:
Function:
Timeout: 30
Runtime: python3.11
Resources:
RDSFunction:
Type: AWS::Serverless::Function
Properties:
Handler: app.handler
CodeUri: function/
Policies:
- RDSFullAccessPolicy:
DBInstanceIdentifier: !Ref DBInstanceIdentifier
Environment:
Variables:
DB_HOST: !GetAtt DBInstance.Endpoint.Address
DB_NAME: !Ref DBName
DB_USER: !Ref MasterUsername
Outputs and Cross-Stack References
Basic Outputs
Outputs:
DBInstanceId:
Description: Database Instance ID
Value: !Ref DBInstance
DBInstanceEndpoint:
Description: Database endpoint address
Value: !GetAtt DBInstance.Endpoint.Address
DBInstancePort:
Description: Database port
Value: !GetAtt DBInstance.Endpoint.Port
DBInstanceArn:
Description: Database Instance ARN
Value: !GetAtt DBInstance.Arn
DBInstanceClass:
Description: Database Instance Class
Value: !Ref DBInstanceClass
Exporting Values for Cross-Stack References
Export values so other stacks can import them.
Outputs:
DBInstanceId:
Description: Database Instance ID for other stacks
Value: !Ref DBInstance
Export:
Name: !Sub ${AWS::StackName}-DBInstanceId
DBInstanceEndpoint:
Description: Database endpoint for application stacks
Value: !GetAtt DBInstance.Endpoint.Address
Export:
Name: !Sub ${AWS::StackName}-DBEndpoint
DBInstancePort:
Description: Database port for application stacks
Value: !GetAtt DBInstance.Endpoint.Port
Export:
Name: !Sub ${AWS::StackName}-DBPort
DBConnectionString:
Description: Full connection string for applications
Value: !Sub jdbc:mysql://${DBInstanceEndpoint}:${DBInstancePort}/${DBName}
Importing Values in Another Stack
Parameters:
DBInstanceId:
Type: AWS::RDS::DBInstance::Id
Description: RDS instance ID from database stack
DBEndpoint:
Type: String
Description: Database endpoint address
Resources:
ApplicationDatabaseConfig:
Type: AWS::SSM::Parameter
Properties:
Name: /app/database/endpoint
Value: !Ref DBEndpoint
Type: String
Cross-Stack Reference Pattern
Create a dedicated database stack that exports values:
AWSTemplateFormatVersion: 2010-09-09
Description: Database infrastructure stack
Parameters:
EnvironmentName:
Type: String
Default: production
Resources:
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: !Sub Subnet group for ${EnvironmentName}
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass.t3.medium: db
Engine: mysql
MasterUsername: admin
MasterUserPassword: !Ref DBPassword
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !Ref DBSecurityGroup
MultiAZ: true
Application stack imports these values:
AWSTemplateFormatVersion: 2010-09-09
Description: Application stack that imports from database stack
Parameters:
DatabaseStackName:
Type: String
Description: Name of the database stack
Default: database-stack
Resources:
ApplicationConfig:
Type: AWS::SSM::Parameter
Properties:
Name: /app/database/endpoint
Value: !ImportValue
Fn::Sub: ${DatabaseStackName}-DBEndpoint
Type: String
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.11
Handler: app.handler
Environment:
Variables:
DB_ENDPOINT: !ImportValue
Fn::Sub: ${DatabaseStackName}-DBEndpoint
RDS Database Components
DB Subnet Group
Required for VPC deployment. Must include at least 2 subnets in different AZs.
Resources:
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for RDS instance
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
- !Ref PrivateSubnet3
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-dbsubnet
DB Parameter Group
Custom parameter groups for database configuration.
Resources:
DBParameterGroup:
Type: AWS::RDS::DBParameterGroup
Properties:
Description: Custom parameter group for MySQL 8.0
Family: mysql8.0
Parameters:
max_connections: 200
max_user_connections: 200
innodb_buffer_pool_size: 1073741824
innodb_buffer_pool_instances: 4
query_cache_type: 1
query_cache_size: 268435456
default_time_zone: "+00:00"
character_set_server: utf8mb4
collation_server: utf8mb4_unicode_ci
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-dbparam
DB Option Group
For database features like Oracle XML or SQL Server features.
Resources:
DBOptionGroup:
Type: AWS::RDS::DBOptionGroup
Properties:
EngineName: oracle-ee
MajorEngineVersion: "19"
OptionGroupDescription: Option group for Oracle 19c
Options:
- OptionName: OEM
OptionVersion: "19"
Port: 5500
VpcSecurityGroupMemberships:
- !Ref OEMSecurityGroup
- OptionName: SSL
OptionSettings:
- Name: SQLNET.SSL_VERSION
Value: "1.2"
DB Instance - MySQL
Resources:
MySQLDBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: mysql-instance
DBInstanceClass: db.t3.medium
Engine: mysql
EngineVersion: "8.0.35"
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
AllocatedStorage: "100"
StorageType: gp3
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !Ref DBSecurityGroup
DBParameterGroupName: !Ref DBParameterGroup
StorageEncrypted: true
MultiAZ: true
BackupRetentionPeriod: 35
DeletionProtection: true
EnablePerformanceInsights: true
PerformanceInsightsRetentionPeriod: 731
AutoMinorVersionUpgrade: false
Tags:
- Key: Environment
Value:
DB Instance - PostgreSQL
Resources:
PostgreSQLDBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: postgres-instance
DBInstanceClass: db.t3.medium
Engine: postgres
EngineVersion: "16.1"
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
AllocatedStorage: "100"
StorageType: gp3
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !Ref DBSecurityGroup
DBParameterGroupName: !Ref DBParameterGroup
StorageEncrypted: true
MultiAZ: true
BackupRetentionPeriod: 35
DeletionProtection: true
EnablePerformanceInsights: true
PubliclyAccessible: false
Aurora MySQL Cluster
Resources:
AuroraMySQLCluster:
Type: AWS::RDS::DBCluster
Properties:
DBClusterIdentifier: aurora-mysql-cluster
Engine: aurora-mysql
EngineVersion: "8.0.mysql_aurora.3.02.0"
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
DatabaseName: mydb
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !Ref DBSecurityGroup
DBClusterParameterGroupName: !Ref AuroraClusterParameterGroup
StorageEncrypted: true
EngineMode: provisioned
Port: 3306
EnableIAMDatabaseAuthentication: true
AuroraDBInstanceWriter:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: aurora-writer
DBClusterIdentifier: !Ref AuroraMySQLCluster
Engine: aurora-mysql
DBInstanceClass: db.r5.large
PromotionTier:
Aurora PostgreSQL Cluster
Resources:
AuroraPostgresCluster:
Type: AWS::RDS::DBCluster
Properties:
DBClusterIdentifier: aurora-pg-cluster
Engine: aurora-postgresql
EngineVersion: "15.4"
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
DatabaseName: mydb
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !Ref DBSecurityGroup
StorageEncrypted: true
EngineMode: provisioned
Port: 5432
AuroraPostgresInstanceWriter:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: aurora-pg-writer
DBClusterIdentifier: !Ref AuroraPostgresCluster
Engine: aurora-postgresql
DBInstanceClass: db.r5.large
PromotionTier: 1
AuroraPostgresInstanceReader:
Type: AWS::RDS::DBInstance
DependsOn:
Aurora Serverless Cluster
Resources:
AuroraServerlessCluster:
Type: AWS::RDS::DBCluster
Properties:
DBClusterIdentifier: aurora-serverless
Engine: aurora-mysql
EngineVersion: "5.6.mysql_aurora.2.12.0"
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
DatabaseName: mydb
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !Ref DBSecurityGroup
EngineMode: serverless
ScalingConfiguration:
AutoPause: true
MinCapacity: 2
MaxCapacity: 32
SecondsUntilAutoPause: 300
DB Cluster Parameter Group (Aurora)
Resources:
AuroraClusterParameterGroup:
Type: AWS::RDS::DBClusterParameterGroup
Properties:
Description: Custom cluster parameter group for Aurora MySQL
Family: aurora-mysql8.0
Parameters:
character_set_server: utf8mb4
collation_server: utf8mb4_unicode_ci
max_connections: 1000
innodb_buffer_pool_size: 2147483648
slow_query_log: "ON"
long_query_time: 2
Security and Secrets
Using Secrets Manager for Credentials
Resources:
DBCredentialsSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub ${AWS::StackName}/rds/credentials
Description: RDS database credentials
SecretString: !Sub |
{
"username": "${MasterUsername}",
"password": "${MasterUserPassword}",
"host": !GetAtt DBInstance.Endpoint.Address,
"port": !GetAtt DBInstance.Endpoint.Port,
"dbname": "mydb"
}
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: db.t3.medium
Engine: mysql
MasterUsername: !Sub "{{resolve:secretsmanager:${DBCredentialsSecret}:SecretString:username}}"
MasterUserPassword: !Sub "{{resolve:secretsmanager:${DBCredentialsSecret}:SecretString:password}}"
DB Security Group (for EC2-Classic)
Resources:
DBSecurityGroup:
Type: AWS::RDS::DBSecurityGroup
Properties:
DBSecurityGroupDescription: Security group for RDS instance
EC2VpcId: !Ref VPCId
DBSecurityGroupIngress:
- EC2SecurityGroupId: !Ref AppSecurityGroup
- EC2SecurityGroupName: default
VPC Security Groups (Recommended)
For VPC deployment, use EC2 security groups instead:
Resources:
DBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for RDS
VpcId: !Ref VPCId
GroupName: !Sub ${AWS::StackName}-rds-sg
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref AppSecurityGroup
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-rds-sg
AppSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for application
VpcId: !Ref VPCId
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
High Availability and Multi-AZ
Multi-AZ Deployment
Parameters:
EnableMultiAZ:
Type: String
Default: true
AllowedValues:
- true
- false
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
MultiAZ: !Ref EnableMultiAZ
AvailabilityZone: !If
- IsMultiAZ
- !Select [1, !GetAZs '']
- !Ref AWS::NoValue
Read Replicas
Resources:
PrimaryDBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: db.r5.large
Engine: mysql
SourceDBInstanceIdentifier: !Ref ExistingDBInstance
CrossRegionReadReplica:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: my-cross-region-replica
SourceDBInstanceIdentifier: !Sub arn:aws:rds:us-west-2:${AWS::AccountId}:db:${PrimaryDBInstance}
DBInstanceClass: db.r5.large
Engine: mysql
Enhanced Monitoring and Performance Insights
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
EnablePerformanceInsights: true
PerformanceInsightsRetentionPeriod: 731
PerformanceInsightsKMSKeyId: !Ref PerformanceInsightsKey
MonitoringInterval: 60
MonitoringRoleArn: !GetAtt MonitoringRole.Arn
EnableCloudwatchLogsExports:
- audit
- error
- general
- slowquery
Resources:
MonitoringRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: monitoring.rds.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole
Best Practices
Use AWS-Specific Parameter Types
Always use AWS-specific parameter types for validation and easier selection.
Parameters:
DBInstanceClass:
Type: AWS::RDS::DBInstance::InstanceType
Description: RDS instance type
DBInstanceIdentifier:
Type: String
AllowedPattern: "^[a-zA-Z][a-zA-Z0-9]*$"
Enable Encryption at Rest
Always enable encryption for production databases.
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
StorageEncrypted: true
KmsKeyId: !Ref EncryptionKey
Use Multi-AZ for Production
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
MultiAZ: !If [IsProduction, true, false]
BackupRetentionPeriod: !If [IsProduction, 35, 7]
DeletionProtection: !If [IsProduction, true, false]
Enable Performance Insights
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
EnablePerformanceInsights: true
PerformanceInsightsRetentionPeriod: 731
PerformanceInsightsKMSKeyId: !Ref PK
Use Proper Naming Conventions
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
Tags:
- Key: Name
Value: !Sub ${Environment}-${Application}-rds
- Key: Environment
Value: !Ref Environment
- Key: Application
Value: !Ref ApplicationName
- Key: ManagedBy
Value: CloudFormation
Use Secrets Manager for Credentials
Resources:
DBCredentials:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub ${AWS::StackName}/rds/credentials
SecretString: !Sub '{"username":"${MasterUsername}","password":"${MasterUserPassword}"}'
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
MasterUsername: !Sub "{{resolve:secretsmanager:${DBCredentials}:SecretString:username}}"
MasterUserPassword: !Sub "{{resolve:secretsmanager:${DBCredentials}:SecretString:password}}"
Separate Database and Application Stacks
AWSTemplateFormatVersion: 2010-09-09
Description: Database infrastructure (VPC, subnets, RDS instance)
Resources:
DBSubnetGroup: AWS::RDS::DBSubnetGroup
DBInstance: AWS::RDS::DBInstance
DBParameterGroup: AWS::RDS::DBParameterGroup
AWSTemplateFormatVersion: 2010-09-09
Description: Application resources
Parameters:
DatabaseStackName:
Type: String
Resources:
ApplicationConfig: AWS::SSM::Parameter
Use Pseudo Parameters
Use pseudo parameters for region-agnostic templates.
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: !Sub ${AWS::StackName}-${AWS::Region}
Tags:
- Key: Region
Value: !Ref AWS::Region
- Key: AccountId
Value: !Ref AWS::AccountId
Validate Before Deployment
aws cloudformation validate-template --template-body file://template.yaml
pip install cfn-lint
cfn-lint template.yaml
cfn-lint template.yaml --region us-east-1
Stack Policies
Stack policies protect critical resources from unintended updates during stack operations. For RDS databases, this is essential to prevent accidental modifications that could cause data loss or downtime.
Basic Stack Policy
{
"Statement" : [
{
"Effect" : "Allow",
"Action" : "Update:*",
"Principal": "*",
"Resource" : "*"
},
{
"Effect" : "Deny",
"Action" : "Update:*",
"Principal": "*",
"Resource" : "LogicalResourceId/DBInstance"
},
{
"Effect" : "Deny",
"Action" : "Update:*",
"Principal": "*",
"Resource" : "LogicalResourceId/DBCluster"
}
]
}
Stack Policy for Production RDS
{
"Statement": [
{
"Effect": "Allow",
"Action": "Update:*",
"Principal": "*",
"Resource": "*"
},
{
"Effect": "Deny",
"Action": [
"Update:Replace",
"Update:Delete"
],
"Principal": "*",
"Resource": "LogicalResourceId/DBInstance"
},
{
"Effect": "Deny",
"Action": [
"Update:Replace",
"Update:Delete"
],
"Principal": "*",
"Resource": "LogicalResourceId/DBCluster"
},
{
"Effect": "Deny",
"Action": "Update:Delete",
"Principal": "*",
"Resource": "LogicalResourceId/DBSubnetGroup"
},
{
"Effect": "Allow",
"Action": "Update:Modify",
"Principal": "*",
"Resource": "LogicalResourceId/DBInstance",
"Condition": {
"StringEquals": {
"ResourceAttribute/StorageEncrypted": "true"
}
}
}
]
}
Setting Stack Policy
aws cloudformation create-stack \
--stack-name my-rds-stack \
--template-body file://template.yaml \
--stack-policy-body file://stack-policy.json
aws cloudformation set-stack-policy \
--stack-name my-rds-stack \
--stack-policy-body file://stack-policy.json
aws cloudformation get-stack-policy \
--stack-name my-rds-stack \
--query StackPolicyBody \
--output text
Termination Protection
Termination protection is critical for RDS databases as it prevents accidental deletion that could result in data loss. This should be enabled for all production databases.
Enabling Termination Protection
aws cloudformation create-stack \
--stack-name production-rds \
--template-body file://template.yaml \
--enable-termination-protection
aws cloudformation update-termination-protection \
--stack-name production-rds \
--enable-termination-protection
aws cloudformation describe-stacks \
--stack-name production-rds \
--query 'Stacks[0].EnableTerminationProtection' \
--output boolean
aws cloudformation update-termination-protection \
--stack-name production-rds \
--no-enable-termination-protection
Termination Protection in Template
AWSTemplateFormatVersion: 2010-09-09
Description: RDS instance with termination protection enabled
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: production-db
DBInstanceClass: db.r5.large
Engine: mysql
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
StorageEncrypted: true
MultiAZ: true
DeletionProtection: true
Deletion Protection vs Termination Protection
| Feature | DeletionProtection | Termination Protection |
|---|
| Level | Resource level (DBInstance) | Stack level |
| Prevents | DELETE_DB_INSTANCE API call | CloudFormation stack deletion |
| Console UI | Instance settings | Stack settings |
| Override | Cannot be overridden | Can be disabled with confirmation |
| Recommended for | All production RDS instances | All production stacks with RDS |
Deletion Protection Best Practice
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
DeletionProtection: !If [IsProduction, true, false]
MultiAZ: !If [IsProduction, true, false]
BackupRetentionPeriod: !If [IsProduction, 35, 7]
Drift Detection
Drift detection identifies when the actual infrastructure configuration differs from the CloudFormation template. This is crucial for RDS to ensure security and compliance.
Detecting Drift
aws cloudformation detect-stack-drift \
--stack-name production-rds
aws cloudformation detect-stack-drift \
--stack-name production-rds \
--logical-resource-ids DBInstance,DBParameterGroup
aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id <detection-id>
aws cloudformation describe-stack-resource-drifts \
--stack-name production-rds
Drift Detection Status Response
{
"StackResourceDrifts": [
{
"LogicalResourceId": "DBInstance",
"PhysicalResourceId": "production-db-instance-id",
"ResourceType": "AWS::RDS::DBInstance",
"StackId": "arn:aws:cloudformation:us-east-1:123456789:stack/production-rds/...",
"DriftStatus": "MODIFIED",
"PropertyDifferences": [
{
"PropertyPath": "MultiAZ",
"ExpectedValue": "true",
"ActualValue": "false"
},
{
"PropertyPath": "BackupRetentionPeriod",
"ExpectedValue": "35",
"ActualValue": "7"
Automated Drift Detection Schedule
aws events put-rule \
--name rds-drift-detection \
--schedule-expression "rate(7 days)"
aws events put-targets \
--rule rds-drift-detection \
--targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:123456789:function/drift-checker"
Drift Detection Script
#!/bin/bash
STACK_NAME=$1
DRIFT_STATUS=$(aws cloudformation detect-stack-drift \
--stack-name $STACK_NAME \
--query StackDriftStatus \
--output text 2>/dev/null)
if [ "$DRIFT_STATUS" == "DRIFTED" ]; then
echo "Drift detected on stack $STACK_NAME"
aws cloudformation describe-stack-resources \
--stack-name $STACK_NAME \
--query 'StackResources[?ResourceStatusReason!=`null`]' \
--output table
aws sns publish \
--topic-arn arn:aws:sns:us-east-1:123456789:rds-drift-alert \
--message "Drift detected on stack $STACK_NAME"
else
echo "No drift detected on stack $STACK_NAME"
fi
Change Sets
Change sets allow you to preview how proposed changes will affect your stack before execution. This is essential for RDS to understand potential impact.
Creating and Viewing a Change Set
aws cloudformation create-change-set \
--stack-name production-rds \
--change-set-name preview-changes \
--template-body file://updated-template.yaml \
--capabilities CAPABILITY_IAM \
--change-set-type UPDATE
aws cloudformation list-change-sets \
--stack-name production-rds
aws cloudformation describe-change-set \
--stack-name production-rds \
--change-set-name preview-changes
aws cloudformation execute-change-set \
--stack-name production-rds \
--change-set-name preview-changes
aws cloudformation delete-change-set \
--stack-name production-rds \
--change-set-name preview-changes
Change Set Response Example
{
"ChangeSetName": "preview-changes",
"ChangeSetId": "arn:aws:cloudformation:us-east-1:123456789:changeSet/...",
"StackId": "arn:aws:cloudformation:us-east-1:123456789:stack/...",
"Status": "CREATE_COMPLETE",
"Changes": [
{
"Type": "Resource",
"ResourceChange": {
"Action": "Modify",
"LogicalResourceId": "DBInstance",
"PhysicalResourceId": "production-db",
"ResourceType": "AWS::RDS::DBInstance",
"Replacement": "False",
"Scope": [
"Properties"
],
Change Set for RDS Modifications
aws cloudformation create-change-set \
--stack-name production-rds \
--change-set-name modify-instance-class \
--template-body file://modify-instance-template.yaml \
--parameters parameter-overrides DBInstanceClass=db.r5.xlarge
aws cloudformation create-change-set \
--stack-name production-rds \
--change-set-name add-read-replica \
--template-body file://add-replica-template.yaml
aws cloudformation create-change-set \
--stack-name production-rds \
--change-set-name change-engine-version \
--template-body file://change-version-template.yaml
Change Set Types
| Change Set Type | Description | Use Case |
|---|
UPDATE | Creates changes for existing stack | Modifying existing resources |
CREATE | Simulates stack creation | Validating new templates |
IMPORT | Imports existing resources | Moving resources to CloudFormation |
Change Set Best Practices for RDS
aws cloudformation create-change-set \
--stack-name production-rds \
--change-set-name pre-update-preview \
--template-body file://updated-template.yaml
aws cloudformation describe-change-set \
--stack-name production-rds \
--change-set-name pre-update-preview \
--query 'Changes[].ResourceChange'
aws cloudformation describe-change-set \
--stack-name production-rds \
--change-set-name pre-update-preview \
--query 'Changes[?ResourceChange.Replacement==`True`]'
aws cloudformation execute-change-set \
--stack-name production-rds \
--change-set-name pre-update-preview
Related Resources