This one's for beginners.
Think about each time you created and configured some new AWS services from the console, got really happy about it when you got it right but in the end thought it's going to be a mess to do it all over again.
I'm here to tell you that Infrastructure as a Code will save your ass so don't fight it. It will hurt in the beginning as it's a tough learning curve but it pays off on any given Sunday.
I decided to write this blogpost after migrating 80+ resources across 11 services into CloudFormation because I know it hurts to start something like this and I wanted to give you the push that you needed.
I'm writing IaC on a regular basis in Terraform and CloudFormation, this time I used CloudFormation because I was curious how the import feature works, since I didn't use until now.
Let's talk about the benefits of importing your existing resources into IaC:
- version control: track every infrastructure change in git
- disaster recovery: recreate your entire infrastructure from code
- consistency: deploy identical environments (dev/staging/prod)
- change preview: see what will change before applying
- team collaboration: code reviews for infrastructure changes
- dependency management: CloudFormation prevents breaking changes
There are a few cons on the other hand:
- console updates: you can still manually modify resources in the console
- drift detection: you need to actively check for drift (manual changes done from the console, not via IaC)
The import process: Overview
- create a CloudFormation template describing your existing resources
- create an import changeset specifying which resources to import
- execute the changeset to bring resources under CloudFormation management
Sounds simple but the devil is in the details.
Lessons learned
1. Never use create-stack for existing resources
aws cloudformation create-stack \
--stack-name network \
--template-body file://network.yaml
CloudFormation creates NEW resources instead of importing existing ones. You'll end up with duplicate VPCs, subnets and a mess to clean up.
Always use import changesets:
aws cloudformation create-change-set \
--stack-name network \
--change-set-name import-network \
--change-set-type IMPORT \
--resources-to-import file://resources.json \
--template-body file://network.yaml
2. Not all resource types support import
CloudFormation import has limitations. Some resource types simply don't support it.
Resources that DON'T support import:
- AWS::Route53::RecordSet - DNS records
- route table associations
- VPC gateway attachments
Workarounds (any will do):
- manage these outside CloudFormation
- delete and recreate them (accept brief downtime)
- use Terraform instead (supports more imports)
Example: For Route53 records, I deleted existing records and let CloudFormation recreate them. The 5-10 second downtime was acceptable for full IaC coverage.
3. DeletionPolicy is mandatory for imports
Use the DeletionPolicy: Retain to every resource definition or you'll get the Resources must have DeletionPolicy attribute specified in the template error
CloudFormation needs to know what to do if you delete the stack. Retain means "keep the resource even if the stack is deleted."
4. Outputs can't be added during import
Use a two-step process to avoid the You cannot modify or add [Outputs] during import operations error:
- Import resources without Outputs section
- Update stack to add Outputs after import completes
5. Resource names must match exactly
Your template uses a friendly name but AWS has a different name. Use exact AWS names during import, rename later via stack update to aviod the The Identifier [AlarmName] does not match the identifier value in the template error.
6. Beware of circular dependencies
Security groups often reference each other:
- SG-A allows traffic from SG-B
- SG-B allows traffic from SG-A
If you use cross-stack references, you create a deadlock:
- Stack A exports SG-A, imports SG-B
- Stack B exports SG-B, imports SG-A
- Neither can be created or updated!
Keep mutually referencing resources in the same stack or as a last resort, use hardcoded IDs for cross references.
7. Start with minimal outputs
Export only what's actually used to aviod dependencies that block stack updates and deletions.
8. Modular stacks are better than monoliths
The monolith approach can break the stack because one change requires updating entire stack, it's difficult to understand and maintain, you have long deployment times:
infrastructure.yaml (5000 lines)
├── VPC
├── Subnets
├── EC2 Instances
├── Security Groups
├── CloudFront
├── Route53
├── S3
└── Everything else
The modular approach:
templates/
├── network.yaml (VPC, subnets)
├── compute.yaml (EC2, security groups)
├── cdn.yaml (CloudFront, WAF)
├── dns.yaml (Route53)
└── storage.yaml (S3)
You update only what changed, have a smaller blast radius, it's easier to understand and you get faster deployments.
Import strategy
Inventory and planning
- document existing resources: list everything you need to import
- check import support: verify each resource type supports import
- identify dependencies: map relationships between resources
- plan stack structure: decide on modular vs monolithic approach
Create import templates
- start simple: begin with standalone resources (S3, SNS)
- add DeletionPolicy: every resource needs DeletionPolicy: Retain
- use exact names: match AWS resource names exactly
- skip Outputs: don't add Outputs section yet
- test in dev: always test import process in non-production first
Execute imports
-
create import changeset: use
--change-set-type IMPORT - review changes: check what will be imported
- execute changeset: bring resources under IaC management
- verify: confirm resources are managed by CloudFormation
Improve templates
- add Outputs: update stacks to add cross-stack exports
-
replace hardcoded values: Use
!ImportValuefor dynamic references - document: add comments explaining configurations
Conclusion
- always use import changesets, never create-stack
- not all resources support import so plan accordingly
-
DeletionPolicyis mandatory for imports - start modular, multiple focused stacks beat monoliths
- minimal exports, avoid dependency hell
- test in dev first: always
- accept brief downtime for resources that don't support import
- document everything, your future self will thank you
Resources
AWS CloudFormation import documentation
Resource types that support import
CloudFormation best practices
Top comments (0)