AWS CloudFormation
AWS CloudFormation is Amazon’s first-party Infrastructure as Code service. You describe a set of AWS resources in a declarative YAML or JSON template, hand it to CloudFormation, and it provisions, updates, and deletes those resources as a single managed unit called a stack. The defining trait is that AWS owns the state for you — there is no state file to store, lock, or back up — and the engine offers AWS-native features like change sets and drift detection. If your footprint is exclusively AWS and you want zero external tooling, CloudFormation is the path of least resistance; if you span multiple clouds or want a richer module ecosystem, Terraform usually wins.
Templates: the unit of declaration
A CloudFormation template is a structured document with a handful of top-level sections — Parameters, Resources, Outputs, and optionally Mappings, Conditions, and Transform. Only Resources is required. Templates support intrinsic functions such as !Ref, !GetAtt, and !Sub for wiring values between resources, which fill the role that interpolation and references play in HCL.
Here is a minimal template that creates an S3 bucket with a parameterized name and exports its ARN:
AWSTemplateFormatVersion: "2010-09-09"
Description: Access-log bucket managed by CloudFormation
Parameters:
BucketName:
Type: String
Default: acme-app-access-logs
Resources:
LogBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
VersioningConfiguration:
Status: Enabled
Tags:
- Key: Environment
Value: prod
- Key: ManagedBy
Value: cloudformation
Outputs:
LogBucketArn:
Description: ARN of the access-log bucket
Value: !GetAtt LogBucket.Arn
Export:
Name: acme-log-bucket-arn
You deploy it as a stack with the AWS CLI:
aws cloudformation deploy \
--template-file logs.yaml \
--stack-name acme-logs \
--parameter-overrides BucketName=acme-app-access-logs
Output:
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - acme-logs
Stacks and change sets
A stack is the deployment boundary: every resource in a template is created, updated, and rolled back together. If a resource fails to create during a stack operation, CloudFormation automatically rolls the whole stack back to its previous good state, so you never end up half-applied — a meaningful safety property compared to a raw terraform apply that errors midway.
A change set is CloudFormation’s equivalent of terraform plan. It computes the difference between your template and the live stack and shows exactly which resources will be added, modified, or replaced before you execute it. Crucially, it flags replacement — a property change that forces the resource to be destroyed and recreated, the same concept HCL surfaces as # forces replacement.
aws cloudformation create-change-set \
--stack-name acme-logs \
--change-set-name add-lifecycle \
--template-file logs.yaml \
--change-set-type UPDATE
aws cloudformation describe-change-set \
--stack-name acme-logs --change-set-name add-lifecycle \
--query 'Changes[].ResourceChange.{Action:Action,Type:ResourceType,Replace:Replacement}'
Output:
[
{
"Action": "Modify",
"Type": "AWS::S3::Bucket",
"Replace": "False"
}
]
Always review a change set before executing it. A property marked
Replacement: Truewill delete and recreate the resource — for a database or stateful bucket that can mean data loss, exactly as it would with a Terraform replacement.
Managed state and drift detection
CloudFormation stores the authoritative state of each stack inside the AWS service itself. There is no terraform.tfstate to put in S3, no DynamoDB lock table to configure, and no risk of a developer running with a stale local copy. Concurrency is handled for you: a stack can only run one operation at a time.
The trade-off for not seeing the state is drift — when someone changes a resource out of band (a console edit, a script, another team). Drift detection asks CloudFormation to compare each resource’s live configuration against the template it deployed:
aws cloudformation detect-stack-drift --stack-name acme-logs
aws cloudformation describe-stack-resource-drifts --stack-name acme-logs \
--stack-resource-drift-status-filters MODIFIED DELETED
This is conceptually the same as a Terraform refresh-only plan, but it is an explicit, on-demand operation rather than something that happens automatically on every plan.
Terraform vs CloudFormation on AWS
Both are mature, declarative, and free to use (you pay only for the resources). The decision usually comes down to cloud breadth, ecosystem, and how much you value AWS-native integration.
| Dimension | CloudFormation | Terraform / OpenTofu |
|---|---|---|
| Cloud scope | AWS only | Multi-cloud and SaaS via 4,000+ providers |
| Language | YAML / JSON templates | HCL (with for_each, expressions, functions) |
| State | Managed by AWS, no state file | Self-managed state file + locking backend |
| Plan preview | Change sets | terraform plan |
| Drift | On-demand drift detection | Refresh-only plan |
| Modularity | Nested stacks, StackSets | Modules + public registry |
| New AWS service support | Same-day, first-party | Usually days behind via the AWS provider |
| Rollback | Automatic on failure | Manual / re-apply |
A practical middle ground: many teams run Terraform but reach for CloudFormation only where AWS requires it (for example, Service Catalog products or some marketplace deployments), or use the AWS CDK, which synthesizes CloudFormation templates from a real programming language. Note that everything Terraform does here applies equally to OpenTofu, its open-source fork.
Best Practices
- Prefer YAML over JSON for templates — it supports comments and the shorthand intrinsic functions (
!Ref,!Sub) that keep templates readable. - Always create and inspect a change set before updating a production stack; never deploy blind.
- Enable termination protection (
aws cloudformation update-termination-protection) on stacks that own stateful resources to prevent accidental deletion. - Run drift detection on a schedule so out-of-band console changes are caught early rather than at the next deploy.
- Break large environments into nested stacks or StackSets instead of one monolithic template to keep blast radius and update times small.
- Use parameters and
Conditionsfor environment differences rather than copy-pasting templates per environment. - If you need multi-cloud, a module registry, or richer logic, evaluate Terraform/OpenTofu before committing to CloudFormation.