Resources: MyWebServer: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: ami-0c55b159cbfafe1f0
Resources: MyWebServer: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: ami-0c55b159cbfafe1f0
Resources: MyWebServer: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: ami-0c55b159cbfafe1f0
AWSTemplateFormatVersion: "2010-09-09"
Description: Simple EC2 instance Resources: MyInstance: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: ami-0c55b159cbfafe1f0 SubnetId: subnet-0abc1234
AWSTemplateFormatVersion: "2010-09-09"
Description: Simple EC2 instance Resources: MyInstance: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: ami-0c55b159cbfafe1f0 SubnetId: subnet-0abc1234
AWSTemplateFormatVersion: "2010-09-09"
Description: Simple EC2 instance Resources: MyInstance: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: ami-0c55b159cbfafe1f0 SubnetId: subnet-0abc1234
AWSTemplateFormatVersion: "2010-09-09"
Description: Portable EC2 instance Parameters: SubnetId: Type: AWS::EC2::Subnet::Id Description: The subnet to launch the instance into Mappings: RegionAMIMap: us-east-1: AMI: ami-0c55b159cbfafe1f0 eu-west-1: AMI: ami-0d71ea30463e0ff49 Resources: MyInstance: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] SubnetId: !Ref SubnetId
AWSTemplateFormatVersion: "2010-09-09"
Description: Portable EC2 instance Parameters: SubnetId: Type: AWS::EC2::Subnet::Id Description: The subnet to launch the instance into Mappings: RegionAMIMap: us-east-1: AMI: ami-0c55b159cbfafe1f0 eu-west-1: AMI: ami-0d71ea30463e0ff49 Resources: MyInstance: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] SubnetId: !Ref SubnetId
AWSTemplateFormatVersion: "2010-09-09"
Description: Portable EC2 instance Parameters: SubnetId: Type: AWS::EC2::Subnet::Id Description: The subnet to launch the instance into Mappings: RegionAMIMap: us-east-1: AMI: ami-0c55b159cbfafe1f0 eu-west-1: AMI: ami-0d71ea30463e0ff49 Resources: MyInstance: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] SubnetId: !Ref SubnetId
Parameters: EnvironmentName: Type: String Default: development AllowedValues: - development - staging - production Description: The environment this stack is being deployed to InstanceType: Type: String Default: t3.micro AllowedValues: - t3.micro - t3.small - t3.medium Description: EC2 instance type DBPassword: Type: String NoEcho: true MinLength: 8 MaxLength: 32 Description: Database password will not be displayed
Parameters: EnvironmentName: Type: String Default: development AllowedValues: - development - staging - production Description: The environment this stack is being deployed to InstanceType: Type: String Default: t3.micro AllowedValues: - t3.micro - t3.small - t3.medium Description: EC2 instance type DBPassword: Type: String NoEcho: true MinLength: 8 MaxLength: 32 Description: Database password will not be displayed
Parameters: EnvironmentName: Type: String Default: development AllowedValues: - development - staging - production Description: The environment this stack is being deployed to InstanceType: Type: String Default: t3.micro AllowedValues: - t3.micro - t3.small - t3.medium Description: EC2 instance type DBPassword: Type: String NoEcho: true MinLength: 8 MaxLength: 32 Description: Database password will not be displayed
Properties: InstanceType: !Ref InstanceType
Properties: InstanceType: !Ref InstanceType
Properties: InstanceType: !Ref InstanceType
Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub "myapp-${AWS::AccountId}-${AWS::Region}-logs"
Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub "myapp-${AWS::AccountId}-${AWS::Region}-logs"
Resources: MyBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub "myapp-${AWS::AccountId}-${AWS::Region}-logs"
Properties: DBSnapshotIdentifier: !If [IsProduction, !Ref SnapshotId, !Ref AWS::NoValue]
Properties: DBSnapshotIdentifier: !If [IsProduction, !Ref SnapshotId, !Ref AWS::NoValue]
Properties: DBSnapshotIdentifier: !If [IsProduction, !Ref SnapshotId, !Ref AWS::NoValue]
# On a parameter: returns the parameter value
InstanceType: !Ref InstanceTypeParam # On a resource: returns the resource's primary identifier
SubnetId: !Ref MySubnet # Returns the Subnet ID of MySubnet
# On a parameter: returns the parameter value
InstanceType: !Ref InstanceTypeParam # On a resource: returns the resource's primary identifier
SubnetId: !Ref MySubnet # Returns the Subnet ID of MySubnet
# On a parameter: returns the parameter value
InstanceType: !Ref InstanceTypeParam # On a resource: returns the resource's primary identifier
SubnetId: !Ref MySubnet # Returns the Subnet ID of MySubnet
# Get the ARN of a Lambda function
FunctionArn: !GetAtt MyLambdaFunction.Arn # Get the DNS name of a load balancer
LoadBalancerDNS: !GetAtt MyALB.DNSName # Get the ARN of an IAM role
RoleArn: !GetAtt MyRole.Arn
# Get the ARN of a Lambda function
FunctionArn: !GetAtt MyLambdaFunction.Arn # Get the DNS name of a load balancer
LoadBalancerDNS: !GetAtt MyALB.DNSName # Get the ARN of an IAM role
RoleArn: !GetAtt MyRole.Arn
# Get the ARN of a Lambda function
FunctionArn: !GetAtt MyLambdaFunction.Arn # Get the DNS name of a load balancer
LoadBalancerDNS: !GetAtt MyALB.DNSName # Get the ARN of an IAM role
RoleArn: !GetAtt MyRole.Arn
# Simple substitution with pseudo parameters
BucketName: !Sub "myapp-${AWS::AccountId}-${AWS::Region}" # Substitution with logical resource references
Description: !Sub "This is the ${EnvironmentName} environment stack" # Substitution with explicit variable map
Command: !Sub - "aws s3 cp s3://${BucketName}/config.json /etc/myapp/config.json" - BucketName: !Ref MyBucket
# Simple substitution with pseudo parameters
BucketName: !Sub "myapp-${AWS::AccountId}-${AWS::Region}" # Substitution with logical resource references
Description: !Sub "This is the ${EnvironmentName} environment stack" # Substitution with explicit variable map
Command: !Sub - "aws s3 cp s3://${BucketName}/config.json /etc/myapp/config.json" - BucketName: !Ref MyBucket
# Simple substitution with pseudo parameters
BucketName: !Sub "myapp-${AWS::AccountId}-${AWS::Region}" # Substitution with logical resource references
Description: !Sub "This is the ${EnvironmentName} environment stack" # Substitution with explicit variable map
Command: !Sub - "aws s3 cp s3://${BucketName}/config.json /etc/myapp/config.json" - BucketName: !Ref MyBucket
# Join with no delimiter
PolicyArn: !Join ["", ["arn:aws:iam::", !Ref AWS::AccountId, ":root"]] # Join with comma delimiter
AllowedOrigins: !Join [",", [!Ref Domain1, !Ref Domain2]]
# Join with no delimiter
PolicyArn: !Join ["", ["arn:aws:iam::", !Ref AWS::AccountId, ":root"]] # Join with comma delimiter
AllowedOrigins: !Join [",", [!Ref Domain1, !Ref Domain2]]
# Join with no delimiter
PolicyArn: !Join ["", ["arn:aws:iam::", !Ref AWS::AccountId, ":root"]] # Join with comma delimiter
AllowedOrigins: !Join [",", [!Ref Domain1, !Ref Domain2]]
# Get the first availability zone in the region
AvailabilityZone: !Select [0, !GetAZs ""] # Get the second
AvailabilityZone: !Select [1, !GetAZs ""]
# Get the first availability zone in the region
AvailabilityZone: !Select [0, !GetAZs ""] # Get the second
AvailabilityZone: !Select [1, !GetAZs ""]
# Get the first availability zone in the region
AvailabilityZone: !Select [0, !GetAZs ""] # Get the second
AvailabilityZone: !Select [1, !GetAZs ""]
ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI]
ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI]
ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI]
InstanceType: !If [IsProduction, m5.xlarge, t3.micro]
InstanceType: !If [IsProduction, m5.xlarge, t3.micro]
InstanceType: !If [IsProduction, m5.xlarge, t3.micro]
Conditions: IsProductionAndEU: !And - !Condition IsProduction - !Equals [!Ref AWS::Region, "eu-west-1"]
Conditions: IsProductionAndEU: !And - !Condition IsProduction - !Equals [!Ref AWS::Region, "eu-west-1"]
Conditions: IsProductionAndEU: !And - !Condition IsProduction - !Equals [!Ref AWS::Region, "eu-west-1"]
VpcId: !ImportValue SharedInfra-VpcId
VpcId: !ImportValue SharedInfra-VpcId
VpcId: !ImportValue SharedInfra-VpcId
SecurityGroupId: !Select - 0 - !Split [",", !ImportValue SharedInfra-SecurityGroupIds]
SecurityGroupId: !Select - 0 - !Split [",", !ImportValue SharedInfra-SecurityGroupIds]
SecurityGroupId: !Select - 0 - !Split [",", !ImportValue SharedInfra-SecurityGroupIds]
Mappings: RegionAMIMap: us-east-1: AMI: ami-0c55b159cbfafe1f0 BastionAMI: ami-0a887e401f7654935 eu-west-1: AMI: ami-0d71ea30463e0ff49 BastionAMI: ami-08d658f84a6d84a80 ap-southeast-1: AMI: ami-01f7527546b557442 BastionAMI: ami-0c5199d385b432989 EnvironmentConfig: development: InstanceType: t3.micro MultiAZ: false DeletionProtection: false production: InstanceType: m5.large MultiAZ: true DeletionProtection: true
Mappings: RegionAMIMap: us-east-1: AMI: ami-0c55b159cbfafe1f0 BastionAMI: ami-0a887e401f7654935 eu-west-1: AMI: ami-0d71ea30463e0ff49 BastionAMI: ami-08d658f84a6d84a80 ap-southeast-1: AMI: ami-01f7527546b557442 BastionAMI: ami-0c5199d385b432989 EnvironmentConfig: development: InstanceType: t3.micro MultiAZ: false DeletionProtection: false production: InstanceType: m5.large MultiAZ: true DeletionProtection: true
Mappings: RegionAMIMap: us-east-1: AMI: ami-0c55b159cbfafe1f0 BastionAMI: ami-0a887e401f7654935 eu-west-1: AMI: ami-0d71ea30463e0ff49 BastionAMI: ami-08d658f84a6d84a80 ap-southeast-1: AMI: ami-01f7527546b557442 BastionAMI: ami-0c5199d385b432989 EnvironmentConfig: development: InstanceType: t3.micro MultiAZ: false DeletionProtection: false production: InstanceType: m5.large MultiAZ: true DeletionProtection: true
Resources: MyInstance: Type: AWS::EC2::Instance Properties: ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] InstanceType: !FindInMap [EnvironmentConfig, !Ref EnvironmentName, InstanceType]
Resources: MyInstance: Type: AWS::EC2::Instance Properties: ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] InstanceType: !FindInMap [EnvironmentConfig, !Ref EnvironmentName, InstanceType]
Resources: MyInstance: Type: AWS::EC2::Instance Properties: ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] InstanceType: !FindInMap [EnvironmentConfig, !Ref EnvironmentName, InstanceType]
Outputs: WebServerPublicIP: Description: Public IP address of the web server Value: !GetAtt MyWebServer.PublicIp LoadBalancerDNS: Description: DNS name for the application load balancer Value: !GetAtt MyALB.DNSName VpcId: Description: VPC ID for use by other stacks Value: !Ref MyVPC Export: Name: !Sub "${AWS::StackName}-VpcId"
Outputs: WebServerPublicIP: Description: Public IP address of the web server Value: !GetAtt MyWebServer.PublicIp LoadBalancerDNS: Description: DNS name for the application load balancer Value: !GetAtt MyALB.DNSName VpcId: Description: VPC ID for use by other stacks Value: !Ref MyVPC Export: Name: !Sub "${AWS::StackName}-VpcId"
Outputs: WebServerPublicIP: Description: Public IP address of the web server Value: !GetAtt MyWebServer.PublicIp LoadBalancerDNS: Description: DNS name for the application load balancer Value: !GetAtt MyALB.DNSName VpcId: Description: VPC ID for use by other stacks Value: !Ref MyVPC Export: Name: !Sub "${AWS::StackName}-VpcId"
# After CloudFormation deploy, grab the load balancer URL
ALB_URL=$(aws cloudformation describe-stacks \ --stack-name my-app \ --query "Stacks[0].Outputs[?OutputKey=='LoadBalancerDNS'].OutputValue" \ --output text) # Use it to run integration tests
curl -f "http://$ALB_URL/health"
# After CloudFormation deploy, grab the load balancer URL
ALB_URL=$(aws cloudformation describe-stacks \ --stack-name my-app \ --query "Stacks[0].Outputs[?OutputKey=='LoadBalancerDNS'].OutputValue" \ --output text) # Use it to run integration tests
curl -f "http://$ALB_URL/health"
# After CloudFormation deploy, grab the load balancer URL
ALB_URL=$(aws cloudformation describe-stacks \ --stack-name my-app \ --query "Stacks[0].Outputs[?OutputKey=='LoadBalancerDNS'].OutputValue" \ --output text) # Use it to run integration tests
curl -f "http://$ALB_URL/health"
Parameters: EnvironmentName: Type: String AllowedValues: [development, production] Conditions: IsProduction: !Equals [!Ref EnvironmentName, production] IsNotProduction: !Not [!Condition IsProduction] Resources: PrimaryDatabase: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: !If [IsProduction, db.m5.large, db.t3.micro] MultiAZ: !If [IsProduction, true, false] DeletionProtection: !If [IsProduction, true, false] NATGateway: Type: AWS::EC2::NatGateway Condition: IsProduction Properties: SubnetId: !Ref PublicSubnet AllocationId: !GetAtt ElasticIP.AllocationId
Parameters: EnvironmentName: Type: String AllowedValues: [development, production] Conditions: IsProduction: !Equals [!Ref EnvironmentName, production] IsNotProduction: !Not [!Condition IsProduction] Resources: PrimaryDatabase: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: !If [IsProduction, db.m5.large, db.t3.micro] MultiAZ: !If [IsProduction, true, false] DeletionProtection: !If [IsProduction, true, false] NATGateway: Type: AWS::EC2::NatGateway Condition: IsProduction Properties: SubnetId: !Ref PublicSubnet AllocationId: !GetAtt ElasticIP.AllocationId
Parameters: EnvironmentName: Type: String AllowedValues: [development, production] Conditions: IsProduction: !Equals [!Ref EnvironmentName, production] IsNotProduction: !Not [!Condition IsProduction] Resources: PrimaryDatabase: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: !If [IsProduction, db.m5.large, db.t3.micro] MultiAZ: !If [IsProduction, true, false] DeletionProtection: !If [IsProduction, true, false] NATGateway: Type: AWS::EC2::NatGateway Condition: IsProduction Properties: SubnetId: !Ref PublicSubnet AllocationId: !GetAtt ElasticIP.AllocationId
Conditions: IsProduction: !Equals [!Ref Env, production] IsUS: !Equals [!Ref AWS::Region, us-east-1] # Both must be true IsProductionUS: !And - !Condition IsProduction - !Condition IsUS # Either must be true IsProductionOrUS: !Or - !Condition IsProduction - !Condition IsUS # Invert IsNotProduction: !Not [!Condition IsProduction]
Conditions: IsProduction: !Equals [!Ref Env, production] IsUS: !Equals [!Ref AWS::Region, us-east-1] # Both must be true IsProductionUS: !And - !Condition IsProduction - !Condition IsUS # Either must be true IsProductionOrUS: !Or - !Condition IsProduction - !Condition IsUS # Invert IsNotProduction: !Not [!Condition IsProduction]
Conditions: IsProduction: !Equals [!Ref Env, production] IsUS: !Equals [!Ref AWS::Region, us-east-1] # Both must be true IsProductionUS: !And - !Condition IsProduction - !Condition IsUS # Either must be true IsProductionOrUS: !Or - !Condition IsProduction - !Condition IsUS # Invert IsNotProduction: !Not [!Condition IsProduction]
Resources: MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 InternetGateway: Type: AWS::EC2::InternetGateway VPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref MyVPC InternetGatewayId: !Ref InternetGateway PublicSubnet: Type: AWS::EC2::Subnet DependsOn: VPCGatewayAttachment Properties: VpcId: !Ref MyVPC CidrBlock: 10.0.1.0/24
Resources: MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 InternetGateway: Type: AWS::EC2::InternetGateway VPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref MyVPC InternetGatewayId: !Ref InternetGateway PublicSubnet: Type: AWS::EC2::Subnet DependsOn: VPCGatewayAttachment Properties: VpcId: !Ref MyVPC CidrBlock: 10.0.1.0/24
Resources: MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 InternetGateway: Type: AWS::EC2::InternetGateway VPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref MyVPC InternetGatewayId: !Ref InternetGateway PublicSubnet: Type: AWS::EC2::Subnet DependsOn: VPCGatewayAttachment Properties: VpcId: !Ref MyVPC CidrBlock: 10.0.1.0/24
Resources: Database: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: db.t3.micro Engine: mysql AppServer: Type: AWS::EC2::Instance DependsOn: Database Properties: UserData: !Base64 !Sub | #!/bin/bash echo "DB_HOST=${Database.Endpoint.Address}" >> /etc/app/config
Resources: Database: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: db.t3.micro Engine: mysql AppServer: Type: AWS::EC2::Instance DependsOn: Database Properties: UserData: !Base64 !Sub | #!/bin/bash echo "DB_HOST=${Database.Endpoint.Address}" >> /etc/app/config
Resources: Database: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: db.t3.micro Engine: mysql AppServer: Type: AWS::EC2::Instance DependsOn: Database Properties: UserData: !Base64 !Sub | #!/bin/bash echo "DB_HOST=${Database.Endpoint.Address}" >> /etc/app/config
Resources: WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle WebServer: Type: AWS::EC2::Instance Properties: ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] InstanceType: t3.micro UserData: !Base64 !Sub | #!/bin/bash -xe # Install and configure application yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "<h1>Hello from ${AWS::StackName}</h1>" > /var/www/html/index.html # Signal CloudFormation that bootstrap is complete /opt/aws/bin/cfn-signal -e $? \ --stack ${AWS::StackName} \ --resource WebServerWaitCondition \ --region ${AWS::Region} WebServerWaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: WebServer Properties: Handle: !Ref WaitHandle Timeout: 600 Count: 1
Resources: WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle WebServer: Type: AWS::EC2::Instance Properties: ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] InstanceType: t3.micro UserData: !Base64 !Sub | #!/bin/bash -xe # Install and configure application yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "<h1>Hello from ${AWS::StackName}</h1>" > /var/www/html/index.html # Signal CloudFormation that bootstrap is complete /opt/aws/bin/cfn-signal -e $? \ --stack ${AWS::StackName} \ --resource WebServerWaitCondition \ --region ${AWS::Region} WebServerWaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: WebServer Properties: Handle: !Ref WaitHandle Timeout: 600 Count: 1
Resources: WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle WebServer: Type: AWS::EC2::Instance Properties: ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] InstanceType: t3.micro UserData: !Base64 !Sub | #!/bin/bash -xe # Install and configure application yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "<h1>Hello from ${AWS::StackName}</h1>" > /var/www/html/index.html # Signal CloudFormation that bootstrap is complete /opt/aws/bin/cfn-signal -e $? \ --stack ${AWS::StackName} \ --resource WebServerWaitCondition \ --region ${AWS::Region} WebServerWaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: WebServer Properties: Handle: !Ref WaitHandle Timeout: 600 Count: 1
Resources: WebServer: Type: AWS::EC2::Instance Metadata: AWS::CloudFormation::Init: config: packages: yum: httpd: [] php: [] files: /var/www/html/index.php: content: !Sub | <?php echo "<h1>Environment: ${EnvironmentName}</h1>"; echo "<p>Stack: ${AWS::StackName}</p>"; ?> mode: "000644" owner: apache group: apache /etc/httpd/conf.d/myapp.conf: content: | <VirtualHost *:80> DocumentRoot /var/www/html DirectoryIndex index.php </VirtualHost> mode: "000644" owner: root group: root services: sysvinit: httpd: enabled: true ensureRunning: true files: - /etc/httpd/conf.d/myapp.conf packages: yum: - httpd Properties: ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] InstanceType: t3.micro UserData: !Base64 !Sub | #!/bin/bash -xe # Run cfn-init to apply the configuration /opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServer \ --region ${AWS::Region} # Signal success or failure /opt/aws/bin/cfn-signal -e $? \ --stack ${AWS::StackName} \ --resource WebServerWaitCondition \ --region ${AWS::Region} WebServerWaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: WebServer Properties: Handle: !Ref WaitHandle Timeout: 600 Count: 1 WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle
Resources: WebServer: Type: AWS::EC2::Instance Metadata: AWS::CloudFormation::Init: config: packages: yum: httpd: [] php: [] files: /var/www/html/index.php: content: !Sub | <?php echo "<h1>Environment: ${EnvironmentName}</h1>"; echo "<p>Stack: ${AWS::StackName}</p>"; ?> mode: "000644" owner: apache group: apache /etc/httpd/conf.d/myapp.conf: content: | <VirtualHost *:80> DocumentRoot /var/www/html DirectoryIndex index.php </VirtualHost> mode: "000644" owner: root group: root services: sysvinit: httpd: enabled: true ensureRunning: true files: - /etc/httpd/conf.d/myapp.conf packages: yum: - httpd Properties: ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] InstanceType: t3.micro UserData: !Base64 !Sub | #!/bin/bash -xe # Run cfn-init to apply the configuration /opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServer \ --region ${AWS::Region} # Signal success or failure /opt/aws/bin/cfn-signal -e $? \ --stack ${AWS::StackName} \ --resource WebServerWaitCondition \ --region ${AWS::Region} WebServerWaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: WebServer Properties: Handle: !Ref WaitHandle Timeout: 600 Count: 1 WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle
Resources: WebServer: Type: AWS::EC2::Instance Metadata: AWS::CloudFormation::Init: config: packages: yum: httpd: [] php: [] files: /var/www/html/index.php: content: !Sub | <?php echo "<h1>Environment: ${EnvironmentName}</h1>"; echo "<p>Stack: ${AWS::StackName}</p>"; ?> mode: "000644" owner: apache group: apache /etc/httpd/conf.d/myapp.conf: content: | <VirtualHost *:80> DocumentRoot /var/www/html DirectoryIndex index.php </VirtualHost> mode: "000644" owner: root group: root services: sysvinit: httpd: enabled: true ensureRunning: true files: - /etc/httpd/conf.d/myapp.conf packages: yum: - httpd Properties: ImageId: !FindInMap [RegionAMIMap, !Ref AWS::Region, AMI] InstanceType: t3.micro UserData: !Base64 !Sub | #!/bin/bash -xe # Run cfn-init to apply the configuration /opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServer \ --region ${AWS::Region} # Signal success or failure /opt/aws/bin/cfn-signal -e $? \ --stack ${AWS::StackName} \ --resource WebServerWaitCondition \ --region ${AWS::Region} WebServerWaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: WebServer Properties: Handle: !Ref WaitHandle Timeout: 600 Count: 1 WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle
Metadata: AWS::CloudFormation::Init: configSets: full_install: - install_cfn - install_base - install_app - configure_app install_cfn: files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} mode: "000400" owner: root group: root install_base: packages: yum: httpd: [] php: [] php-mysqlnd: [] install_app: files: /var/www/html/index.php: content: !Sub | <?php phpinfo(); ?> mode: "000644" owner: apache group: apache configure_app: services: sysvinit: httpd: enabled: true ensureRunning: true
Metadata: AWS::CloudFormation::Init: configSets: full_install: - install_cfn - install_base - install_app - configure_app install_cfn: files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} mode: "000400" owner: root group: root install_base: packages: yum: httpd: [] php: [] php-mysqlnd: [] install_app: files: /var/www/html/index.php: content: !Sub | <?php phpinfo(); ?> mode: "000644" owner: apache group: apache configure_app: services: sysvinit: httpd: enabled: true ensureRunning: true
Metadata: AWS::CloudFormation::Init: configSets: full_install: - install_cfn - install_base - install_app - configure_app install_cfn: files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} mode: "000400" owner: root group: root install_base: packages: yum: httpd: [] php: [] php-mysqlnd: [] install_app: files: /var/www/html/index.php: content: !Sub | <?php phpinfo(); ?> mode: "000644" owner: apache group: apache configure_app: services: sysvinit: httpd: enabled: true ensureRunning: true
/opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServer \ --configsets full_install \ --region ${AWS::Region}
/opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServer \ --configsets full_install \ --region ${AWS::Region}
/opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServer \ --configsets full_install \ --region ${AWS::Region}
Metadata: AWS::CloudFormation::Init: configSets: full_install: - install_cfn - install_app install_cfn: files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} interval=5 mode: "000400" owner: root group: root /etc/cfn/hooks.d/cfn-auto-reloader.conf: content: !Sub | [cfn-auto-reloader-hook] triggers=post.update path=Resources.WebServer.Metadata.AWS::CloudFormation::Init action=/opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServer \ --configsets full_install \ --region ${AWS::Region} runas=root mode: "000400" owner: root group: root services: sysvinit: cfn-hup: enabled: true ensureRunning: true files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.conf
Metadata: AWS::CloudFormation::Init: configSets: full_install: - install_cfn - install_app install_cfn: files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} interval=5 mode: "000400" owner: root group: root /etc/cfn/hooks.d/cfn-auto-reloader.conf: content: !Sub | [cfn-auto-reloader-hook] triggers=post.update path=Resources.WebServer.Metadata.AWS::CloudFormation::Init action=/opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServer \ --configsets full_install \ --region ${AWS::Region} runas=root mode: "000400" owner: root group: root services: sysvinit: cfn-hup: enabled: true ensureRunning: true files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.conf
Metadata: AWS::CloudFormation::Init: configSets: full_install: - install_cfn - install_app install_cfn: files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} interval=5 mode: "000400" owner: root group: root /etc/cfn/hooks.d/cfn-auto-reloader.conf: content: !Sub | [cfn-auto-reloader-hook] triggers=post.update path=Resources.WebServer.Metadata.AWS::CloudFormation::Init action=/opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource WebServer \ --configsets full_install \ --region ${AWS::Region} runas=root mode: "000400" owner: root group: root services: sysvinit: cfn-hup: enabled: true ensureRunning: true files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.conf
Resources: NetworkStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://s3.amazonaws.com/mybucket/templates/network.yaml Parameters: EnvironmentName: !Ref EnvironmentName VpcCidr: 10.0.0.0/16 TimeoutInMinutes: 20 AppStack: Type: AWS::CloudFormation::Stack DependsOn: NetworkStack Properties: TemplateURL: https://s3.amazonaws.com/mybucket/templates/app.yaml Parameters: EnvironmentName: !Ref EnvironmentName VpcId: !GetAtt NetworkStack.Outputs.VpcId SubnetIds: !GetAtt NetworkStack.Outputs.SubnetIds TimeoutInMinutes: 30 DatabaseStack: Type: AWS::CloudFormation::Stack DependsOn: NetworkStack Properties: TemplateURL: https://s3.amazonaws.com/mybucket/templates/database.yaml Parameters: SubnetIds: !GetAtt NetworkStack.Outputs.SubnetIds DBPassword: !Ref DBPassword
Resources: NetworkStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://s3.amazonaws.com/mybucket/templates/network.yaml Parameters: EnvironmentName: !Ref EnvironmentName VpcCidr: 10.0.0.0/16 TimeoutInMinutes: 20 AppStack: Type: AWS::CloudFormation::Stack DependsOn: NetworkStack Properties: TemplateURL: https://s3.amazonaws.com/mybucket/templates/app.yaml Parameters: EnvironmentName: !Ref EnvironmentName VpcId: !GetAtt NetworkStack.Outputs.VpcId SubnetIds: !GetAtt NetworkStack.Outputs.SubnetIds TimeoutInMinutes: 30 DatabaseStack: Type: AWS::CloudFormation::Stack DependsOn: NetworkStack Properties: TemplateURL: https://s3.amazonaws.com/mybucket/templates/database.yaml Parameters: SubnetIds: !GetAtt NetworkStack.Outputs.SubnetIds DBPassword: !Ref DBPassword
Resources: NetworkStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://s3.amazonaws.com/mybucket/templates/network.yaml Parameters: EnvironmentName: !Ref EnvironmentName VpcCidr: 10.0.0.0/16 TimeoutInMinutes: 20 AppStack: Type: AWS::CloudFormation::Stack DependsOn: NetworkStack Properties: TemplateURL: https://s3.amazonaws.com/mybucket/templates/app.yaml Parameters: EnvironmentName: !Ref EnvironmentName VpcId: !GetAtt NetworkStack.Outputs.VpcId SubnetIds: !GetAtt NetworkStack.Outputs.SubnetIds TimeoutInMinutes: 30 DatabaseStack: Type: AWS::CloudFormation::Stack DependsOn: NetworkStack Properties: TemplateURL: https://s3.amazonaws.com/mybucket/templates/database.yaml Parameters: SubnetIds: !GetAtt NetworkStack.Outputs.SubnetIds DBPassword: !Ref DBPassword
# network.yaml Outputs section
Outputs: VpcId: Value: !Ref MyVPC SubnetIds: Value: !Join [",", [!Ref SubnetA, !Ref SubnetB]]
# network.yaml Outputs section
Outputs: VpcId: Value: !Ref MyVPC SubnetIds: Value: !Join [",", [!Ref SubnetA, !Ref SubnetB]]
# network.yaml Outputs section
Outputs: VpcId: Value: !Ref MyVPC SubnetIds: Value: !Join [",", [!Ref SubnetA, !Ref SubnetB]]
Outputs: VpcId: Description: VPC ID for use by application stacks Value: !Ref MyVPC Export: Name: SharedNetwork-VpcId PrivateSubnetIds: Description: Comma-separated private subnet IDs Value: !Join [",", [!Ref PrivateSubnetA, !Ref PrivateSubnetB]] Export: Name: SharedNetwork-PrivateSubnetIds AppSecurityGroup: Description: Security group for application instances Value: !Ref AppSG Export: Name: SharedNetwork-AppSecurityGroupId
Outputs: VpcId: Description: VPC ID for use by application stacks Value: !Ref MyVPC Export: Name: SharedNetwork-VpcId PrivateSubnetIds: Description: Comma-separated private subnet IDs Value: !Join [",", [!Ref PrivateSubnetA, !Ref PrivateSubnetB]] Export: Name: SharedNetwork-PrivateSubnetIds AppSecurityGroup: Description: Security group for application instances Value: !Ref AppSG Export: Name: SharedNetwork-AppSecurityGroupId
Outputs: VpcId: Description: VPC ID for use by application stacks Value: !Ref MyVPC Export: Name: SharedNetwork-VpcId PrivateSubnetIds: Description: Comma-separated private subnet IDs Value: !Join [",", [!Ref PrivateSubnetA, !Ref PrivateSubnetB]] Export: Name: SharedNetwork-PrivateSubnetIds AppSecurityGroup: Description: Security group for application instances Value: !Ref AppSG Export: Name: SharedNetwork-AppSecurityGroupId
Resources: AppServer: Type: AWS::EC2::Instance Properties: SubnetId: !Select - 0 - !Split [",", !ImportValue SharedNetwork-PrivateSubnetIds] SecurityGroupIds: - !ImportValue SharedNetwork-AppSecurityGroupId VpcId: !ImportValue SharedNetwork-VpcId
Resources: AppServer: Type: AWS::EC2::Instance Properties: SubnetId: !Select - 0 - !Split [",", !ImportValue SharedNetwork-PrivateSubnetIds] SecurityGroupIds: - !ImportValue SharedNetwork-AppSecurityGroupId VpcId: !ImportValue SharedNetwork-VpcId
Resources: AppServer: Type: AWS::EC2::Instance Properties: SubnetId: !Select - 0 - !Split [",", !ImportValue SharedNetwork-PrivateSubnetIds] SecurityGroupIds: - !ImportValue SharedNetwork-AppSecurityGroupId VpcId: !ImportValue SharedNetwork-VpcId
Export: Name: !Sub "${AWS::StackName}-VpcId"
Export: Name: !Sub "${AWS::StackName}-VpcId"
Export: Name: !Sub "${AWS::StackName}-VpcId"
# CLI command to deploy a StackSet
aws cloudformation create-stack-set \ --stack-set-name SecurityBaseline \ --template-url https://s3.amazonaws.com/mybucket/security-baseline.yaml \ --permission-model SERVICE_MANAGED \ --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false
# CLI command to deploy a StackSet
aws cloudformation create-stack-set \ --stack-set-name SecurityBaseline \ --template-url https://s3.amazonaws.com/mybucket/security-baseline.yaml \ --permission-model SERVICE_MANAGED \ --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false
# CLI command to deploy a StackSet
aws cloudformation create-stack-set \ --stack-set-name SecurityBaseline \ --template-url https://s3.amazonaws.com/mybucket/security-baseline.yaml \ --permission-model SERVICE_MANAGED \ --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false
# Deploy to specific accounts and regions
aws cloudformation create-stack-instances \ --stack-set-name SecurityBaseline \ --accounts 111111111111 222222222222 333333333333 \ --regions us-east-1 eu-west-1 ap-southeast-1 \ --operation-preferences MaxConcurrentPercentage=25,FailureTolerancePercentage=10
# Deploy to specific accounts and regions
aws cloudformation create-stack-instances \ --stack-set-name SecurityBaseline \ --accounts 111111111111 222222222222 333333333333 \ --regions us-east-1 eu-west-1 ap-southeast-1 \ --operation-preferences MaxConcurrentPercentage=25,FailureTolerancePercentage=10
# Deploy to specific accounts and regions
aws cloudformation create-stack-instances \ --stack-set-name SecurityBaseline \ --accounts 111111111111 222222222222 333333333333 \ --regions us-east-1 eu-west-1 ap-southeast-1 \ --operation-preferences MaxConcurrentPercentage=25,FailureTolerancePercentage=10
Resources: ProductionDatabase: Type: AWS::RDS::DBInstance DeletionPolicy: Snapshot Properties: DBInstanceClass: db.m5.large Engine: mysql DBName: myapp LogBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: BucketName: myapp-logs TempQueue: Type: AWS::SQS::Queue DeletionPolicy: Delete Properties: QueueName: myapp-temp
Resources: ProductionDatabase: Type: AWS::RDS::DBInstance DeletionPolicy: Snapshot Properties: DBInstanceClass: db.m5.large Engine: mysql DBName: myapp LogBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: BucketName: myapp-logs TempQueue: Type: AWS::SQS::Queue DeletionPolicy: Delete Properties: QueueName: myapp-temp
Resources: ProductionDatabase: Type: AWS::RDS::DBInstance DeletionPolicy: Snapshot Properties: DBInstanceClass: db.m5.large Engine: mysql DBName: myapp LogBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: BucketName: myapp-logs TempQueue: Type: AWS::SQS::Queue DeletionPolicy: Delete Properties: QueueName: myapp-temp
Resources: Database: Type: AWS::RDS::DBInstance DeletionPolicy: Snapshot UpdateReplacePolicy: Snapshot Properties: DBInstanceClass: db.m5.large
Resources: Database: Type: AWS::RDS::DBInstance DeletionPolicy: Snapshot UpdateReplacePolicy: Snapshot Properties: DBInstanceClass: db.m5.large
Resources: Database: Type: AWS::RDS::DBInstance DeletionPolicy: Snapshot UpdateReplacePolicy: Snapshot Properties: DBInstanceClass: db.m5.large
aws cloudformation create-stack \ --stack-name my-app \ --template-url https://s3.amazonaws.com/mybucket/template.yaml \ --role-arn arn:aws:iam::123456789012:role/CloudFormationDeployRole
aws cloudformation create-stack \ --stack-name my-app \ --template-url https://s3.amazonaws.com/mybucket/template.yaml \ --role-arn arn:aws:iam::123456789012:role/CloudFormationDeployRole
aws cloudformation create-stack \ --stack-name my-app \ --template-url https://s3.amazonaws.com/mybucket/template.yaml \ --role-arn arn:aws:iam::123456789012:role/CloudFormationDeployRole
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "cloudformation.amazonaws.com" }, "Action": "sts:AssumeRole" } ]
}
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "cloudformation.amazonaws.com" }, "Action": "sts:AssumeRole" } ]
}
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "cloudformation.amazonaws.com" }, "Action": "sts:AssumeRole" } ]
}
# Create a ChangeSet
aws cloudformation create-change-set \ --stack-name my-production-app \ --change-set-name planned-update-2024-04 \ --template-url https://s3.amazonaws.com/mybucket/template-v2.yaml \ --parameters ParameterKey=InstanceType,ParameterValue=m5.large # Review the ChangeSet
aws cloudformation describe-change-set \ --stack-name my-production-app \ --change-set-name planned-update-2024-04 # Execute if acceptable
aws cloudformation execute-change-set \ --stack-name my-production-app \ --change-set-name planned-update-2024-04
# Create a ChangeSet
aws cloudformation create-change-set \ --stack-name my-production-app \ --change-set-name planned-update-2024-04 \ --template-url https://s3.amazonaws.com/mybucket/template-v2.yaml \ --parameters ParameterKey=InstanceType,ParameterValue=m5.large # Review the ChangeSet
aws cloudformation describe-change-set \ --stack-name my-production-app \ --change-set-name planned-update-2024-04 # Execute if acceptable
aws cloudformation execute-change-set \ --stack-name my-production-app \ --change-set-name planned-update-2024-04
# Create a ChangeSet
aws cloudformation create-change-set \ --stack-name my-production-app \ --change-set-name planned-update-2024-04 \ --template-url https://s3.amazonaws.com/mybucket/template-v2.yaml \ --parameters ParameterKey=InstanceType,ParameterValue=m5.large # Review the ChangeSet
aws cloudformation describe-change-set \ --stack-name my-production-app \ --change-set-name planned-update-2024-04 # Execute if acceptable
aws cloudformation execute-change-set \ --stack-name my-production-app \ --change-set-name planned-update-2024-04
Action: Modify
LogicalResourceId: WebServer
ResourceType: AWS::EC2::Instance
Replacement: True
Scope: [Properties]
Details: - Attribute: Properties Name: InstanceType RequiresRecreation: Always
Action: Modify
LogicalResourceId: WebServer
ResourceType: AWS::EC2::Instance
Replacement: True
Scope: [Properties]
Details: - Attribute: Properties Name: InstanceType RequiresRecreation: Always
Action: Modify
LogicalResourceId: WebServer
ResourceType: AWS::EC2::Instance
Replacement: True
Scope: [Properties]
Details: - Attribute: Properties Name: InstanceType RequiresRecreation: Always
Resources: CustomConfigLookup: Type: AWS::CloudFormation::CustomResource Properties: ServiceToken: !GetAtt ConfigLookupFunction.Arn Environment: !Ref EnvironmentName ConfigKey: database/endpoint ConfigLookupFunction: Type: AWS::Lambda::Function Properties: Runtime: python3.12 Handler: index.handler Role: !GetAtt LambdaRole.Arn Code: ZipFile: | import json import boto3 import urllib3 def handler(event, context): http = urllib3.PoolManager() try: request_type = event['RequestType'] props = event['ResourceProperties'] if request_type in ['Create', 'Update']: # Look up value from SSM or external API ssm = boto3.client('ssm') key = f"/{props['Environment']}/{props['ConfigKey']}" value = ssm.get_parameter(Name=key)['Parameter']['Value'] send_response(http, event, 'SUCCESS', { 'ConfigValue': value }) elif request_type == 'Delete': # Nothing to clean up for a lookup send_response(http, event, 'SUCCESS', {}) except Exception as e: send_response(http, event, 'FAILED', {}, str(e)) def send_response(http, event, status, data, reason=""): body = json.dumps({ 'Status': status, 'Reason': reason, 'PhysicalResourceId': event.get('PhysicalResourceId', 'custom-resource'), 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'], 'Data': data }) http.request('PUT', event['ResponseURL'], body=body, headers={'Content-Type': 'application/json'}) # Reference the returned value in another resource Database: Type: AWS::RDS::DBInstance Properties: DBInstanceIdentifier: !GetAtt CustomConfigLookup.ConfigValue
Resources: CustomConfigLookup: Type: AWS::CloudFormation::CustomResource Properties: ServiceToken: !GetAtt ConfigLookupFunction.Arn Environment: !Ref EnvironmentName ConfigKey: database/endpoint ConfigLookupFunction: Type: AWS::Lambda::Function Properties: Runtime: python3.12 Handler: index.handler Role: !GetAtt LambdaRole.Arn Code: ZipFile: | import json import boto3 import urllib3 def handler(event, context): http = urllib3.PoolManager() try: request_type = event['RequestType'] props = event['ResourceProperties'] if request_type in ['Create', 'Update']: # Look up value from SSM or external API ssm = boto3.client('ssm') key = f"/{props['Environment']}/{props['ConfigKey']}" value = ssm.get_parameter(Name=key)['Parameter']['Value'] send_response(http, event, 'SUCCESS', { 'ConfigValue': value }) elif request_type == 'Delete': # Nothing to clean up for a lookup send_response(http, event, 'SUCCESS', {}) except Exception as e: send_response(http, event, 'FAILED', {}, str(e)) def send_response(http, event, status, data, reason=""): body = json.dumps({ 'Status': status, 'Reason': reason, 'PhysicalResourceId': event.get('PhysicalResourceId', 'custom-resource'), 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'], 'Data': data }) http.request('PUT', event['ResponseURL'], body=body, headers={'Content-Type': 'application/json'}) # Reference the returned value in another resource Database: Type: AWS::RDS::DBInstance Properties: DBInstanceIdentifier: !GetAtt CustomConfigLookup.ConfigValue
Resources: CustomConfigLookup: Type: AWS::CloudFormation::CustomResource Properties: ServiceToken: !GetAtt ConfigLookupFunction.Arn Environment: !Ref EnvironmentName ConfigKey: database/endpoint ConfigLookupFunction: Type: AWS::Lambda::Function Properties: Runtime: python3.12 Handler: index.handler Role: !GetAtt LambdaRole.Arn Code: ZipFile: | import json import boto3 import urllib3 def handler(event, context): http = urllib3.PoolManager() try: request_type = event['RequestType'] props = event['ResourceProperties'] if request_type in ['Create', 'Update']: # Look up value from SSM or external API ssm = boto3.client('ssm') key = f"/{props['Environment']}/{props['ConfigKey']}" value = ssm.get_parameter(Name=key)['Parameter']['Value'] send_response(http, event, 'SUCCESS', { 'ConfigValue': value }) elif request_type == 'Delete': # Nothing to clean up for a lookup send_response(http, event, 'SUCCESS', {}) except Exception as e: send_response(http, event, 'FAILED', {}, str(e)) def send_response(http, event, status, data, reason=""): body = json.dumps({ 'Status': status, 'Reason': reason, 'PhysicalResourceId': event.get('PhysicalResourceId', 'custom-resource'), 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'], 'Data': data }) http.request('PUT', event['ResponseURL'], body=body, headers={'Content-Type': 'application/json'}) # Reference the returned value in another resource Database: Type: AWS::RDS::DBInstance Properties: DBInstanceIdentifier: !GetAtt CustomConfigLookup.ConfigValue
Template Parameters → Make templates reusable across environments
Pseudo Parameters → Make templates reusable across regions and accounts
Mappings → Resolve environment-specific values at deploy time
Conditions → Create or configure resources conditionally
Intrinsic Functions → Wire resources together and build dynamic values
Outputs → Surface useful values and enable cross-stack sharing
Cross-Stack References → Share foundation infrastructure across independent stacks
Nested Stacks → Decompose large templates into manageable units
StackSets → Deploy consistently across accounts and regions
DependsOn → Control creation order for implicit dependencies
Wait Conditions + cfn-signal → Pause until application bootstrap is complete
cfn-init → Declare instance configuration instead of scripting it
cfn-hup → Keep instance configuration in sync with template changes
Deletion Policy → Protect data on stack deletion or replacement
Stack Roles → Enforce least privilege for infrastructure deployments
ChangeSets → Preview changes before applying them in production
Custom Resources → Extend CloudFormation to anything Lambda can do
Template Parameters → Make templates reusable across environments
Pseudo Parameters → Make templates reusable across regions and accounts
Mappings → Resolve environment-specific values at deploy time
Conditions → Create or configure resources conditionally
Intrinsic Functions → Wire resources together and build dynamic values
Outputs → Surface useful values and enable cross-stack sharing
Cross-Stack References → Share foundation infrastructure across independent stacks
Nested Stacks → Decompose large templates into manageable units
StackSets → Deploy consistently across accounts and regions
DependsOn → Control creation order for implicit dependencies
Wait Conditions + cfn-signal → Pause until application bootstrap is complete
cfn-init → Declare instance configuration instead of scripting it
cfn-hup → Keep instance configuration in sync with template changes
Deletion Policy → Protect data on stack deletion or replacement
Stack Roles → Enforce least privilege for infrastructure deployments
ChangeSets → Preview changes before applying them in production
Custom Resources → Extend CloudFormation to anything Lambda can do
Template Parameters → Make templates reusable across environments
Pseudo Parameters → Make templates reusable across regions and accounts
Mappings → Resolve environment-specific values at deploy time
Conditions → Create or configure resources conditionally
Intrinsic Functions → Wire resources together and build dynamic values
Outputs → Surface useful values and enable cross-stack sharing
Cross-Stack References → Share foundation infrastructure across independent stacks
Nested Stacks → Decompose large templates into manageable units
StackSets → Deploy consistently across accounts and regions
DependsOn → Control creation order for implicit dependencies
Wait Conditions + cfn-signal → Pause until application bootstrap is complete
cfn-init → Declare instance configuration instead of scripting it
cfn-hup → Keep instance configuration in sync with template changes
Deletion Policy → Protect data on stack deletion or replacement
Stack Roles → Enforce least privilege for infrastructure deployments
ChangeSets → Preview changes before applying them in production
Custom Resources → Extend CloudFormation to anything Lambda can do - What is CloudFormation and Why It Matters
- Logical and Physical Resources
- Templates: Portable vs Non-Portable
- Template Parameters and Pseudo Parameters
- Intrinsic Functions
- Wait Conditions and cfn-signal
- Nested Stacks
- Cross-Stack References
- Deletion Policy
- Stack Roles
- Custom Resources - Repeatability The same template deployed ten times produces ten identical environments.
- Version control Your infrastructure lives in Git. Every change is tracked. Every rollback is a git revert.
- Accountability Who changed the security group? Check the commit history.
- Speed A 40-resource stack that takes two hours to click through manually deploys in minutes.
- Disaster recovery When a region fails, you re-deploy the template in another region. Your infrastructure is code, not memory.
- Cost Stacks can be deleted entirely after use. Temporary environments cost nothing when they are gone. - Update a resource when you change the template
- Replace a resource when a change requires replacement
- Delete all physical resources when you delete the stack - CloudFormation reads your template.
- It builds a dependency graph of all logical resources.
- It creates physical resources in the correct order.
- It maps each logical resource to its new physical resource.
- The stack reaches CREATE_COMPLETE. - In your account, same region: Works.
- In your account, different region: Fails. AMI ID does not exist.
- In a colleague's account: Fails. Subnet ID does not exist.
- In a CI/CD pipeline targeting staging: Fails. Both IDs are wrong. - Parameters Inputs you provide at deploy time
- Pseudo Parameters Values AWS provides automatically (account ID, region, etc.)
- Intrinsic Functions Functions that resolve values dynamically
- Mappings Lookup tables built into the template - When an AMI is updated, you must update the mapping manually and redeploy.
- You cannot have a different mapping value per account without writing account IDs into the template. - Visibility Display useful information about what was created (endpoint URLs, resource IDs, ARNs).
- Cross-Stack References Export a value so other stacks can import it. - You create an EC2 instance with a UserData script that runs your bootstrap logic.
- At the end of the script, you call cfn-signal to send a success or failure signal.
- CloudFormation sees the signal and either continues or fails the stack. - cfn-init applies configuration at launch
- cfn-signal tells CloudFormation that configuration is complete
- cfn-hup keeps configuration in sync with the template over time - The network.yaml template has an Outputs section that exports VpcId.
- The parent references it via !GetAtt NetworkStack.Outputs.VpcId.
- The parent passes it as a parameter to AppStack. - Least privilege violation. Your identity has permissions far beyond what it needs for day-to-day work, just because it needs to be able to deploy infrastructure.
- Privilege escalation risk. A CloudFormation template can create IAM roles. If you can deploy any template, you can create an IAM role with AdministratorAccess and assume it. - Action - Add, Modify, or Remove
- Replacement - True, False, or Conditional
- Scope - Which properties are changing
- Details - The specific changes - Populate an S3 bucket with default content after it is created
- Register an AMI from a snapshot and get back the AMI ID to use in your template
- Look up a value from an external API during stack creation
- Perform a database migration as part of a stack update
- Create a resource in a third-party system (Datadog, PagerDuty, Cloudflare) - CloudFormation encounters the Custom Resource during stack operations.
- It sends an HTTPS request to the Lambda function with an event containing the operation type (Create, Update, Delete), the resource properties, and a pre-signed S3 URL to send the response to.
- Your Lambda function performs the custom logic.
- The Lambda sends a JSON response to the S3 URL indicating success or failure, and optionally returning data attributes.
- CloudFormation reads the response and either continues or fails the stack.