tessl install github:giuseppe-trisciuoglio/developer-kit --skill aws-cloudformation-rdsgithub.com/giuseppe-trisciuoglio/developer-kit
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.
Review Score
72%
Validation Score
10/16
Implementation Score
50%
Activation Score
100%
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.
Use this skill when:
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.PortAWSTemplateFormatVersion: 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.ReadEndpointAWS 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 creationThe AWSTemplateFormatVersion identifies the template version. Current version is 2010-09-09.
AWSTemplateFormatVersion: 2010-09-09
Description: My RDS Database TemplateAdd 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 enabledUse 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 UsernameThe 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 DBParameterGroupUse 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 instanceCommon 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.2xlargeAdd 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: 65535Parameters:
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-postgresql14Reference 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/versionUse 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: 41Use 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
# ...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]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]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 MasterUsernameOutputs:
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 DBInstanceClassExport 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}-DBConnectionStringParameters:
# 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: StringCreate 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}-DBSubnetGroupNameApplication 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}-DBEndpointRequired 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}-dbsubnetCustom 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}-dbparamFor 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"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 EnvironmentResources:
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: falseResources:
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: 2Resources:
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: 2Resources:
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: 300Resources:
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: 2Resources:
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}}"
# ...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: defaultFor 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 DBSecurityGroupParameters:
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)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: mysqlResources:
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/AmazonRDSEnhancedMonitoringRoleAlways 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]*$"Always enable encryption for production databases.
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
StorageEncrypted: true
KmsKeyId: !Ref EncryptionKeyConditions:
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]Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
EnablePerformanceInsights: true
PerformanceInsightsRetentionPeriod: 731
PerformanceInsightsKMSKeyId: !Ref PKResources:
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: CloudFormationResources:
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}}"# 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::ParameterUse 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 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-1Stack 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.
{
"Statement" : [
{
"Effect" : "Allow",
"Action" : "Update:*",
"Principal": "*",
"Resource" : "*"
},
{
"Effect" : "Deny",
"Action" : "Update:*",
"Principal": "*",
"Resource" : "LogicalResourceId/DBInstance"
},
{
"Effect" : "Deny",
"Action" : "Update:*",
"Principal": "*",
"Resource" : "LogicalResourceId/DBCluster"
}
]
}{
"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"
}
}
}
]
}# 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 textTermination 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.
# 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-protectionAWSTemplateFormatVersion: 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| 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 |
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 identifies when the actual infrastructure configuration differs from the CloudFormation template. This is crucial for RDS to ensure security and compliance.
# 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{
"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"
}
]
}
]
}# 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"#!/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"
fiChange sets allow you to preview how proposed changes will affect your stack before execution. This is essential for RDS to understand potential impact.
# 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{
"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 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 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 |
# 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