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: Subnet group for RDS SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2
DBInstance: Type: AWS::RDS::DBInstance Properties: DBInstanceIdentifier: !Ref DBInstanceIdentifier DBInstanceClass: !Ref DBInstanceClass Engine: mysql MasterUsername: !Ref MasterUsername MasterUserPassword: !Ref MasterUserPassword DBSubnetGroupName: !Ref DBSubnetGroup VPCSecurityGroups: - !Ref DBSecurityGroup AllocatedStorage: "20" StorageType: gp3 MultiAZ: false
Outputs: DBInstanceEndpoint: Description: Database endpoint address Value: !GetAtt DBInstance.Endpoint.Address
DBInstancePort: Description: Database port Value: !GetAtt DBInstance.Endpoint.Port
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 Engine: aurora-mysql MasterUsername: !Ref MasterUsername MasterUserPassword: !Ref MasterUserPassword DBSubnetGroupName: !Ref DBSubnetGroup VPCSecurityGroups: - !Ref DBSecurityGroup DatabaseName: mydb EngineMode: provisioned Port: 3306
DBInstanceWriter: Type: AWS::RDS::DBInstance Properties: DBInstanceIdentifier: !Sub ${DBClusterIdentifier}-writer DBClusterIdentifier: !Ref DBCluster Engine: aurora-mysql DBInstanceClass: db.t3.medium
DBInstanceReader: Type: AWS::RDS::DBInstance DependsOn: DBInstanceWriter Properties: DBInstanceIdentifier: !Sub ${DBClusterIdentifier}-reader DBClusterIdentifier: !Ref DBCluster Engine: aurora-mysql DBInstanceClass: db.t3.medium PromotionTier: 2
Outputs: ClusterEndpoint: Description: Writer endpoint Value: !GetAtt DBCluster.Endpoint
ReaderEndpoint: Description: Reader endpoint Value: !GetAtt DBCluster.ReadEndpoint
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 # Required - template version Description: Optional description string # Optional description
Section order matters for readability but CloudFormation accepts any order
Mappings: {} # Static configuration tables Metadata: {} # Additional information about resources Parameters: {} # Input values for customization Rules: {} # Parameter validation rules Conditions: {} # Conditional resource creation Transform: {} # Macro processing (e.g., AWS::Serverless) Resources: {} # AWS resources to create (REQUIRED) Outputs: {} # Return values after stack creation
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:
DB Subnet Group (required for VPC deployment)
DBSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: Subnet group for RDS deployment SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2
DB Parameter Group
DBParameterGroup: Type: AWS::RDS::DBParameterGroup Properties: Description: Custom parameter group for MySQL Family: mysql8.0 Parameters: max_connections: 200 innodb_buffer_pool_size: 1073741824
DB Instance
DBInstance: Type: AWS::RDS::DBInstance Properties: DBInstanceIdentifier: mydbinstance DBInstanceClass: db.t3.micro Engine: mysql MasterUsername: admin MasterUserPassword: !Ref DBPassword DBSubnetGroupName: !Ref DBSubnetGroup DBParameterGroupName: !Ref DBParameterGroup
Parameters
Parameter Types
Use AWS-specific parameter types for validation and easier selection in the console.
Parameters:
DB instance identifier
DBInstanceIdentifier: Type: String Description: Database instance identifier
AWS-specific parameter types for validation
DBInstanceClass: Type: AWS::RDS::DBInstance::InstanceType Description: RDS instance class Default: db.t3.micro
Engine version from SSM
EngineVersion: Type: AWS::RDS::DBInstance::Version Description: Database engine version Default: 8.0
For existing VPC security groups
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 AllowedPattern: "[a-zA-Z0-9]*"
AllocatedStorage: Type: Number Description: Allocated storage in GB Default: 20 MinValue: 20 MaxValue: 65536
DBPort: Type: Number Description: Database port Default: 3306 MinValue: 1150 MaxValue: 65535
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 - mysql5.7 - mysql8.0 - postgres11 - postgres12 - postgres13 - postgres14 - postgres15 - postgres16 - aurora5.6 - aurora-mysql5.7 - aurora-mysql8.0 - aurora-postgresql11 - aurora-postgresql14
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 DBInstanceClass, CPU] Engine: mysql # ...
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: !Ref EnableMultiAZ StorageEncrypted: !Ref EnableEncryption # Production gets automated backups BackupRetentionPeriod: !If [IsProduction, 35, 7] DeletionProtection: !If [IsProduction, true, false]
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: # Use license-included for production LicenseModel: !If [HasLicense, "license-included", "bring-your-own-license"] # Production uses provisioned IOPS 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} Export: Name: !Sub ${AWS::StackName}-DBConnectionString
Importing Values in Another Stack
Parameters:
Import via AWS::RDS::DBInstance::Id for console selection
DBInstanceId: Type: AWS::RDS::DBInstance::Id Description: RDS instance ID from database stack
Or use Fn::ImportValue for programmatic access
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:
database-stack.yaml
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 StorageEncrypted: true
Outputs: DBInstanceId: Value: !Ref DBInstance Export: Name: !Sub ${EnvironmentName}-DBInstanceId
DBEndpoint: Value: !GetAtt DBInstance.Endpoint.Address Export: Name: !Sub ${EnvironmentName}-DBEndpoint
DBArn: Value: !GetAtt DBInstance.Arn Export: Name: !Sub ${EnvironmentName}-DBArn
DBSubnetGroupName: Value: !Ref DBSubnetGroup Export: Name: !Sub ${EnvironmentName}-DBSubnetGroupName
Application stack imports these values:
application-stack.yaml
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: # Connection settings max_connections: 200 max_user_connections: 200
# Memory settings
innodb_buffer_pool_size: 1073741824
innodb_buffer_pool_instances: 4
# Query cache (MySQL 5.7)
query_cache_type: 1
query_cache_size: 268435456
# Timezone
default_time_zone: "+00:00"
# Character set
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: !Ref Environment
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: 1
AuroraDBInstanceReader: Type: AWS::RDS::DBInstance DependsOn: AuroraDBInstanceWriter Properties: DBInstanceIdentifier: aurora-reader DBClusterIdentifier: !Ref AuroraMySQLCluster Engine: aurora-mysql DBInstanceClass: db.r5.large PromotionTier: 2
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: AuroraPostgresInstanceWriter Properties: DBInstanceIdentifier: aurora-pg-reader DBClusterIdentifier: !Ref AuroraPostgresCluster Engine: aurora-postgresql DBInstanceClass: db.r5.large PromotionTier: 2
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 # For EC2-Classic, use DBSecurityGroupIngress 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 DestinationSecurityGroupId: !Ref DBSecurityGroup
High Availability and Multi-AZ
Multi-AZ Deployment
Parameters: EnableMultiAZ: Type: String Default: true AllowedValues: - true - false
Resources: DBInstance: Type: AWS::RDS::DBInstance Properties: # Multi-AZ is not supported for Aurora clusters (automatic) MultiAZ: !Ref EnableMultiAZ # For multi-AZ, use a standby in a different AZ AvailabilityZone: !If - IsMultiAZ - !Select [1, !GetAZs ''] - !Ref AWS::NoValue # For single-AZ, specify no AZ (AWS selects)
Read Replicas
Resources:
Primary instance
PrimaryDBInstance: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: db.r5.large Engine: mysql SourceDBInstanceIdentifier: !Ref ExistingDBInstance
Read replica in different region
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
# Enhanced Monitoring
MonitoringInterval: 60
MonitoringRoleArn: !GetAtt MonitoringRole.Arn
# Database insights
EnableCloudwatchLogsExports:
- audit
- error
- general
- slowquery
IAM Role for Enhanced Monitoring
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
database-stack.yaml - Rarely changes
AWSTemplateFormatVersion: 2010-09-09 Description: Database infrastructure (VPC, subnets, RDS instance) Resources: DBSubnetGroup: AWS::RDS::DBSubnetGroup DBInstance: AWS::RDS::DBInstance DBParameterGroup: AWS::RDS::DBParameterGroup
application-stack.yaml - Changes frequently
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
Validate template
aws cloudformation validate-template --template-body file://template.yaml
Use cfn-lint for advanced validation
pip install cfn-lint cfn-lint template.yaml
Check for AWS-specific issues
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
Set stack policy during creation
aws cloudformation create-stack
--stack-name my-rds-stack
--template-body file://template.yaml
--stack-policy-body file://stack-policy.json
Set stack policy on existing stack
aws cloudformation set-stack-policy
--stack-name my-rds-stack
--stack-policy-body file://stack-policy.json
View current stack policy
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
Enable termination protection on stack creation
aws cloudformation create-stack
--stack-name production-rds
--template-body file://template.yaml
--enable-termination-protection
Enable termination protection on existing stack
aws cloudformation update-termination-protection
--stack-name production-rds
--enable-termination-protection
Check if termination protection is enabled
aws cloudformation describe-stacks
--stack-name production-rds
--query 'Stacks[0].EnableTerminationProtection'
--output boolean
Disable termination protection (requires confirmation)
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 # Termination protection is set at stack level, not resource level
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: # Always enable deletion protection DeletionProtection: !If [IsProduction, true, false] # Additional production safeguards 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
Detect drift on entire stack
aws cloudformation detect-stack-drift
--stack-name production-rds
Detect drift on specific resources
aws cloudformation detect-stack-drift
--stack-name production-rds
--logical-resource-ids DBInstance,DBParameterGroup
Get drift detection status
aws cloudformation describe-stack-drift-detection-status
--stack-drift-detection-id <detection-id>
Check drift status for all resources
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
Create a Lambda function to check drift weekly
and send SNS notification if drift is detected
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
check-rds-drift.sh
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
Send notification
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
Create change set for stack update
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
List change sets for a stack
aws cloudformation list-change-sets
--stack-name production-rds
Describe change set
aws cloudformation describe-change-set
--stack-name production-rds
--change-set-name preview-changes
Execute change set
aws cloudformation execute-change-set
--stack-name production-rds
--change-set-name preview-changes
Delete change set (if not executing)
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" ], "Details": [ { "Target": { "Attribute": "Properties", "Name": "MultiAZ" }, "Evaluation": "Static", "ChangeSource": "Parameter", "BeforeValue": "false", "AfterValue": "true" } ] } } ] }
Change Set for RDS Modifications
Change set that will modify RDS instance class
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
Change set for adding read replica
aws cloudformation create-change-set
--stack-name production-rds
--change-set-name add-read-replica
--template-body file://add-replica-template.yaml
Change set that requires replacement (causes downtime)
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
Always create change set before updating RDS
aws cloudformation create-change-set
--stack-name production-rds
--change-set-name pre-update-preview
--template-body file://updated-template.yaml
Review changes carefully
aws cloudformation describe-change-set
--stack-name production-rds
--change-set-name pre-update-preview
--query 'Changes[].ResourceChange'
Check for replacement operations
aws cloudformation describe-change-set
--stack-name production-rds
--change-set-name pre-update-preview
--query 'Changes[?ResourceChange.Replacement==True]'
Only execute if changes are acceptable
aws cloudformation execute-change-set
--stack-name production-rds
--change-set-name pre-update-preview
Related Resources
-
For advanced patterns: See EXAMPLES.md
-
For reference: See REFERENCE.md
-
AWS CloudFormation User Guide: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/
-
RDS Documentation: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/
-
RDS Best Practices: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_BestPractices.html
-
Aurora Documentation: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/