Automation and IaC: CloudFormation, CDK and Systems Manager
Deploy a template as a CloudFormation stack, preview updates with a change set, catch manual edits with drift detection, and know where CDK and Systems Manager fit.
By the end of this lesson you'll be able to
0 of 6 completeWhy not click
Lessons 3 to 5 built servers, networks and load balancers by clicking. Clicking doesn't scale: you can't review it, repeat it in another Region, or know what changed last Tuesday. Infrastructure as code describes resources in files that live in git, go through pull requests, and deploy the same way every time.
On AWS you have two families:
| Tool | Writes | Deploys through |
|---|---|---|
| CloudFormation | YAML or JSON templates | CloudFormation stacks |
| AWS CDK | TypeScript, Python, Java, Go… | generates CloudFormation |
| Terraform | HCL | its own engine and state file (the Terraform course) |
This lesson uses CloudFormation, because it's built in and everything else on AWS builds on it. Everything below ran in a real account and was deleted afterwards.
A template
AWSTemplateFormatVersion: '2010-09-09' Description: DevOps Academy - a versioned bucket and a config parameter Parameters: Environment: Type: String AllowedValues: [dev, prod] Default: dev Resources: AssetsBucket: Type: AWS::S3::Bucket Properties: VersioningConfiguration: Status: Enabled PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true LogLevelParam: Type: AWS::SSM::Parameter Properties: Name: !Sub /academy/${Environment}/log-level Type: String Value: info Outputs: BucketName: Value: !Ref AssetsBucket
| Section | Holds |
|---|---|
Parameters | inputs, so one template serves dev and prod |
Resources | what to create: a logical ID, a Type, and its Properties |
Outputs | values to show or pass to other stacks |
!Ref and !Sub are intrinsic functions: !Ref AssetsBucket becomes the bucket's real name, and
!Sub fills ${Environment} into the string. No bucket name is set, so CloudFormation generates a unique one.
Deploy it as a stack
$ aws cloudformation validate-template --template-body file://stack.yaml --query 'Parameters[].ParameterKey' --output text Environment $ aws cloudformation create-stack --stack-name academy-iac --template-body file://stack.yaml \ --parameters ParameterKey=Environment,ParameterValue=dev $ aws cloudformation wait stack-create-complete --stack-name academy-iac $ aws cloudformation describe-stacks --stack-name academy-iac --query 'Stacks[0].[StackStatus,Outputs[0].OutputValue]' --output text CREATE_COMPLETE academy-iac-assetsbucket-2hizm9a4hixg
aws cloudformation validate-template --template-body file://stack.yaml --query 'Parameters[].ParameterKey' --output textawsthe AWS command line--querypick fields from the JSON response--outputoutput format
If a resource fails to create, CloudFormation rolls back: it deletes everything it made in that attempt, so
you're never left with half a stack. The reason is in the Events tab, on the first CREATE_FAILED line.
Change sets: preview before you change
Edit the template: add a tag to the bucket and change the log level to warn. Then ask CloudFormation what it
would do, without doing it:
$ aws cloudformation create-change-set --stack-name academy-iac --change-set-name add-tag-warn-logs \ --template-body file://stack-v2.yaml --parameters ParameterKey=Environment,UsePreviousValue=true $ aws cloudformation describe-change-set --stack-name academy-iac --change-set-name add-tag-warn-logs \ --query 'Changes[].ResourceChange.[Action,LogicalResourceId,Replacement]' --output text Modify AssetsBucket False Modify LogLevelParam False
aws cloudformation create-change-set --stack-name academy-iac --change-set-name add-tag-warn-logs \awsthe AWS command line
The column to read is Replacement. Some property changes can't be done in place, so CloudFormation creates a new resource and deletes the old one: for a database, that means a new, empty database. A change set is how you see that before it happens.
$ aws cloudformation execute-change-set --stack-name academy-iac --change-set-name add-tag-warn-logs $ aws cloudformation wait stack-update-complete --stack-name academy-iac $ aws ssm get-parameter --name /academy/dev/log-level --query Parameter.Value --output text warn
aws cloudformation execute-change-set --stack-name academy-iac --change-set-name add-tag-warn-logsawsthe AWS command line
aws ssm get-parameter --name /academy/dev/log-level --query Parameter.Value --output text--querypick fields from the JSON response--outputoutput format
Drift: when someone clicks in the console
Now someone "quickly fixes" the value by hand, outside the template:
$ aws ssm put-parameter --name /academy/dev/log-level --value debug --overwrite $ DID=$(aws cloudformation detect-stack-drift --stack-name academy-iac --query StackDriftDetectionId --output text) $ aws cloudformation describe-stack-resource-drifts --stack-name academy-iac \ --query 'StackResourceDrifts[].[LogicalResourceId,StackResourceDriftStatus]' --output text AssetsBucket IN_SYNC LogLevelParam MODIFIED
aws ssm put-parameter --name /academy/dev/log-level --value debug --overwriteawsthe AWS command line
Drift is a problem waiting for the next deploy: the next update sets warn again and the manual fix silently
disappears. Either put the change in the template, or undo it. In production, block console edits of
stack-managed resources with IAM, and run drift detection on a schedule.
CDK and Terraform
AWS CDK writes the same stack in a programming language:
const bucket = new s3.Bucket(this, 'AssetsBucket', { versioned: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, }); new ssm.StringParameter(this, 'LogLevelParam', { parameterName: `/academy/${props.environment}/log-level`, stringValue: 'info', });
cdk synth turns it into a CloudFormation template and cdk deploy deploys it as a stack, with change sets
behind the scenes. Its constructs bundle good defaults (like Block Public Access above), and loops and functions
replace copy-paste. Terraform works across clouds and keeps its own state; teams that already use it for
GitHub, Cloudflare or Datadog often use it for AWS too. Pick one per team and stick to it.
Systems Manager: run the fleet
IaC creates servers; AWS Systems Manager works on them once they run, through the SSM agent that Amazon Linux
already has (the instance needs a role with AmazonSSMManagedInstanceCore):
| Feature | Does |
|---|---|
| Run Command | runs a document (such as AWS-RunShellScript) on many instances at once, by tag, and records the output |
| Session Manager | a shell in the browser or CLI with no SSH port or keys (lesson 3), every session logged |
| Patch Manager | scans and installs OS patches on a schedule, by patch baseline and maintenance window |
| Parameter Store | configuration values like /academy/dev/log-level (lesson 15 covers secrets) |
$ aws ssm send-command --document-name AWS-RunShellScript --targets Key=tag:Name,Values=academy-web \ --parameters 'commands=["systemctl is-active nginx"]'
aws ssm send-command --document-name AWS-RunShellScript --targets Key=tag:Name,Values=academy-web \awsthe AWS command line
AWS CI/CD services
The CI/CD course used GitHub Actions. AWS has its own pipeline services, common in companies that keep everything inside AWS:
| Service | Like |
|---|---|
| CodeBuild | a build runner: runs buildspec.yml in a container, pay per minute |
| CodePipeline | the workflow: source → build → deploy stages, with manual approvals |
| CodeDeploy | deploys to EC2, Auto Scaling groups, ECS or Lambda, with rolling or blue/green strategies |
Many teams mix them: GitHub Actions for CI, assuming an AWS role with OIDC (lesson 2), then cloudformation deploy or CodeDeploy for the release.
Clean up
$ aws cloudformation delete-stack --stack-name academy-iac $ aws cloudformation wait stack-delete-complete --stack-name academy-iac $ aws ssm get-parameter --name /academy/dev/log-level An error occurred (ParameterNotFound) when calling the GetParameter operation:
aws cloudformation delete-stack --stack-name academy-iacawsthe AWS command line
Deleting the stack deleted the bucket and the parameter with it. One catch: CloudFormation can't delete a bucket
that still has objects, so empty it first, or set DeletionPolicy: Retain on data you mean to keep.
Nested stacks keep one system understandable
A nested stack is a child stack created by a parent. Put network, application and monitoring in separate templates, then pass outputs such as a subnet ID between them. Use StackSets when the same template must be copied across accounts or Regions.
Resources: Network: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://example.invalid/network.yaml Parameters: {EnvironmentName: academy-demo} Outputs: VpcId: {Value: !GetAtt Network.Outputs.VpcId}
Replace the example URL with a real reviewed template URL before deploying; it is not claimed output. Validate
with cfn-lint, inspect the change set, and delete the demonstration stack.
StackSets and CDK solve different repetition problems
StackSets applies one CloudFormation template to many accounts or Regions. CDK generates CloudFormation from typed
constructs. Bootstrap once, inspect cdk synth, review cdk diff, and deploy only after approval.
$ cfn-lint parent.yaml network.yaml $ cdk synth > template.json $ cdk diff
cdk synth > template.json>write output to a file, replacing it
Template, change set, drift, delete
Deploy this lesson's template as a stack, preview and execute a change with a change set, change the parameter by hand and catch it with drift detection, then delete the stack and prove its resources are gone.
CREATE_COMPLETE, a change set with Replacement False, LogLevelParam MODIFIED in drift detection, then ParameterNotFound after deletionReveal one safe solution
$ aws cloudformation create-stack --stack-name academy-iac --template-body file://stack.yaml $ aws cloudformation wait stack-create-complete --stack-name academy-iac $ sed -e 's/Value: info/Value: warn/' stack.yaml > stack-v2.yaml $ aws cloudformation create-change-set --stack-name academy-iac --change-set-name warn-logs \ --template-body file://stack-v2.yaml --parameters ParameterKey=Environment,UsePreviousValue=true $ aws cloudformation wait change-set-create-complete --stack-name academy-iac --change-set-name warn-logs $ aws cloudformation describe-change-set --stack-name academy-iac --change-set-name warn-logs \ --query 'Changes[].ResourceChange.[Action,LogicalResourceId,Replacement]' --output text $ aws cloudformation execute-change-set --stack-name academy-iac --change-set-name warn-logs $ aws cloudformation wait stack-update-complete --stack-name academy-iac $ aws ssm put-parameter --name /academy/dev/log-level --value debug --overwrite $ DID=$(aws cloudformation detect-stack-drift --stack-name academy-iac --query StackDriftDetectionId --output text) $ sleep 20; aws cloudformation describe-stack-resource-drifts --stack-name academy-iac \ --query 'StackResourceDrifts[].[LogicalResourceId,StackResourceDriftStatus]' --output text $ aws cloudformation delete-stack --stack-name academy-iac && aws cloudformation wait stack-delete-complete --stack-name academy-iac
aws cloudformation create-stack --stack-name academy-iac --template-body file://stack.yamlawsthe AWS command line
sed -e 's/Value: info/Value: warn/' stack.yaml > stack-v2.yaml>write output to a file, replacing it
aws cloudformation delete-stack --stack-name academy-iac && aws cloudformation wait stack-delete-complete --stack-name academy-iac&&run the next command only if this one worked
Everything in this stack is free: an empty bucket and a standard parameter. If drift detection still says
DETECTION_IN_PROGRESS, wait a few more seconds and ask again.
Try it yourself
0 of 6 steps doneRun each task in your own account or terminal, then tick it off.
Quick check
What is a CloudFormation stack?
Official reading for this lesson
Or get every quiz answer right and it completes itself.