Complete AWS Control Tower + CDK Teardown Guide
β οΈ WARNING: This will permanently delete all resources, data, and accounts. Make sure you have backups of any important data before proceeding.
Phase 1: Destroy CDK Applications (CRITICAL - Do This First!)
1.1 Quick CDK Resource Cleanup
# Navigate to your project directory
cd simple-control-tower-cdk
# Load environment variables (if available)
source .env 2>/dev/null || echo "No .env file found"
# Destroy all CDK stacks in reverse order (prod β dev)
cdk destroy HelloWorld-prod --force
cdk destroy HelloWorld-shared --force
cdk destroy HelloWorld-staging --force
cdk destroy HelloWorld-dev --force
# Alternative: Destroy all stacks at once
cdk destroy --all --force
1.2 Automated Teardown Script
Create and run this script for complete CDK cleanup:
#!/bin/bash
# File: scripts/teardown-all.sh
echo "π₯ Starting Complete AWS Control Tower Teardown"
echo "==============================================="
# Load environment variables
source .env 2>/dev/null || echo "β οΈ No .env file found"
# Function to destroy stack with retry
destroy_stack() {
local stack_name="$1"
local account_id="$2"
echo "ποΈ Destroying $stack_name..."
# Try to destroy with specific account context
if [ ! -z "$account_id" ]; then
cdk destroy $stack_name \
--context accountId=$account_id \
--force \
--require-approval never
else
cdk destroy $stack_name --force --require-approval never
fi
if [ $? -eq 0 ]; then
echo "β
$stack_name destroyed successfully"
else
echo "β οΈ Failed to destroy $stack_name (may not exist)"
fi
}
# Destroy application stacks
destroy_stack "HelloWorld-prod" "$PROD_ACCOUNT_ID"
destroy_stack "HelloWorld-shared" "$SHARED_ACCOUNT_ID"
destroy_stack "HelloWorld-staging" "$STAGING_ACCOUNT_ID"
destroy_stack "HelloWorld-dev" "$DEV_ACCOUNT_ID"
# Clean up any remaining CDK bootstrap stacks (optional)
echo "π§Ή Cleaning up CDK Bootstrap stacks..."
echo "β οΈ Only run this if you're not using CDK for other projects:"
echo "cdk destroy CDKToolkit --force"
echo "β
CDK teardown complete!"
# Make script executable and run
chmod +x scripts/teardown-all.sh
./scripts/teardown-all.sh
Phase 2: Manual CloudFormation Cleanup
2.1 Check for Remaining Stacks
# List all CloudFormation stacks in your region
aws cloudformation list-stacks \
--stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE \
--query 'StackSummaries[?contains(StackName, `HelloWorld`) || contains(StackName, `CDK`)].{Name:StackName,Status:StackStatus}'
# πΈπ¬ Singapore users:
# aws cloudformation list-stacks --region ap-southeast-1 \
# --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE \
# --query 'StackSummaries[?contains(StackName, `HelloWorld`) || contains(StackName, `CDK`)].{Name:StackName,Status:StackStatus}'
2.2 Force Delete Remaining Stacks
# Delete any remaining HelloWorld stacks
aws cloudformation delete-stack --stack-name HelloWorld-prod
aws cloudformation delete-stack --stack-name HelloWorld-staging
aws cloudformation delete-stack --stack-name HelloWorld-dev
aws cloudformation delete-stack --stack-name HelloWorld-shared
# Wait for deletion to complete
aws cloudformation wait stack-delete-complete --stack-name HelloWorld-prod
aws cloudformation wait stack-delete-complete --stack-name HelloWorld-staging
aws cloudformation wait stack-delete-complete --stack-name HelloWorld-dev
aws cloudformation wait stack-delete-complete --stack-name HelloWorld-shared
# If needed, delete CDK bootstrap stack (ONLY if not used elsewhere)
# aws cloudformation delete-stack --stack-name CDKToolkit
Phase 3: Account-by-Account Resource Cleanup
3.1 Switch to Each Account and Clean Up
For each workload account (dev, staging, prod, shared), you need to:
# Method 1: If you have cross-account roles set up
aws sts assume-role \
--role-arn "arn:aws:iam::ACCOUNT-ID:role/OrganizationAccountAccessRole" \
--role-session-name "cleanup-session"
# Method 2: Use separate AWS profiles for each account
aws configure set profile.dev.region us-east-1 # or ap-southeast-1 for Singapore
aws configure set profile.dev.account ACCOUNT-ID
# For each account, check for remaining resources:
aws --profile dev lambda list-functions
aws --profile dev apigatewayv2 get-apis
aws --profile dev logs describe-log-groups
aws --profile dev cloudformation list-stacks
3.2 Manual Resource Cleanup (If CDK Destroy Failed)
# Clean up Lambda functions
aws lambda list-functions --query 'Functions[?contains(FunctionName, `HelloWorld`) || contains(FunctionName, `Health`)].FunctionName' --output text | xargs -r -n1 aws lambda delete-function --function-name
# Clean up API Gateways
aws apigatewayv2 get-apis --query 'Items[?contains(Name, `Hello World`)].ApiId' --output text | xargs -r -n1 aws apigatewayv2 delete-api --api-id
# Clean up Log Groups
aws logs describe-log-groups --query 'logGroups[?contains(logGroupName, `hello-world`)].logGroupName' --output text | xargs -r -n1 aws logs delete-log-group --log-group-name
# Clean up IAM roles (created by CDK)
aws iam list-roles --query 'Roles[?contains(RoleName, `HelloWorld`)].RoleName' --output text | xargs -r -n1 aws iam delete-role --role-name
Phase 4: Control Tower Teardown
4.1 Decommission Landing Zone
β οΈ CRITICAL: This will delete ALL accounts created by Control Tower!
# Check Control Tower status first
aws controltower list-landing-zones
# Get the landing zone identifier
LANDING_ZONE_ID=$(aws controltower list-landing-zones --query 'landingZones[0].arn' --output text)
echo "Found Landing Zone: $LANDING_ZONE_ID"
# Initiate landing zone deletion (THIS IS IRREVERSIBLE!)
aws controltower delete-landing-zone --landing-zone-identifier $LANDING_ZONE_ID
# Monitor deletion progress
aws controltower get-landing-zone-operation --operation-identifier OPERATION-ID
4.2 Manual Control Tower Teardown (Alternative)
If CLI method fails, use AWS Console:
Go to Control Tower Console
- πΈπ¬ Singapore: https://ap-southeast-1.console.aws.amazon.com/controltower/
- Other regions: https://console.aws.amazon.com/controltower/
Navigate to Landing Zone Settings
- Click "Landing zone settings" in left sidebar
- Click "Delete landing zone"
Confirm Deletion
- Type "delete" to confirm
- Wait 30-60 minutes for complete deletion
Phase 5: Account Cleanup
5.1 Handle Created Accounts
Control Tower created these accounts that need manual closure:
- Development account (
your-email+dev@gmail.com) - Staging account (
your-email+staging@gmail.com) - Production account (
your-email+prod@gmail.com) - Shared Services account (
your-email+shared@gmail.com) - Audit account (
your-email+audit@gmail.com) - Log Archive account (
your-email+logs@gmail.com)
5.2 Close AWS Accounts
For each created account:
Sign in to each account separately
- Use the email address for that account
- Reset password if needed
Close the account
- Go to Account Settings: https://console.aws.amazon.com/billing/home#/account
- Scroll to "Close Account" section
- Follow the closure process
- β οΈ Important: You may have a 90-day cooling period
Alternative: Remove from Organization
# From your management account aws organizations list-accounts aws organizations remove-account-from-organization --account-id ACCOUNT-ID
Phase 6: Final Cleanup
6.1 Clean Up Management Account
# Remove any remaining CloudFormation stacks
aws cloudformation list-stacks --query 'StackSummaries[?StackStatus!=`DELETE_COMPLETE`].StackName' --output text
# Clean up Service Catalog (if Control Tower used it)
aws servicecatalog list-portfolios
aws servicecatalog list-products
# Clean up Config rules (created by Control Tower)
aws configservice describe-configuration-recorders
aws configservice describe-delivery-channels
# Clean up CloudTrail (created by Control Tower)
aws cloudtrail describe-trails
6.2 Clean Up Local Environment
# Remove project directory
cd ..
rm -rf simple-control-tower-cdk
# Clean up AWS credentials (optional)
aws configure list-profiles
# Remove specific profiles if needed:
# aws configure --profile dev remove region
# aws configure --profile staging remove region
# etc.
# Clean up CDK global installation (optional)
npm uninstall -g aws-cdk
Phase 7: Verification
7.1 Verify Complete Cleanup
# Check for any remaining resources
aws cloudformation list-stacks --query 'StackSummaries[?StackStatus!=`DELETE_COMPLETE`]'
aws lambda list-functions
aws apigatewayv2 get-apis
aws logs describe-log-groups --query 'logGroups[?contains(logGroupName, `hello-world`)]'
# Check Control Tower status
aws controltower list-landing-zones
# Check organizations
aws organizations list-accounts
7.2 Cost Verification
Check AWS Cost Explorer
- https://console.aws.amazon.com/cost-management/home
- Verify no ongoing charges
- πΈπ¬ Singapore: Check for SGD charges
Check each account's billing
- Sign into each account before closure
- Verify $0 balance
- Download final invoices if needed
Emergency: If Something Goes Wrong
Force Delete Everything
#!/bin/bash
# Nuclear option - use with extreme caution
echo "π¨ EMERGENCY FORCE CLEANUP - THIS WILL DELETE EVERYTHING!"
read -p "Are you sure? Type 'DESTROY EVERYTHING' to continue: " confirm
if [ "$confirm" = "DESTROY EVERYTHING" ]; then
# Force delete all CloudFormation stacks
aws cloudformation list-stacks --query 'StackSummaries[?StackStatus!=`DELETE_COMPLETE`].StackName' --output text | xargs -r -n1 aws cloudformation delete-stack --stack-name
# Force delete all Lambda functions
aws lambda list-functions --query 'Functions[].FunctionName' --output text | xargs -r -n1 aws lambda delete-function --function-name
# Force delete all API Gateways
aws apigatewayv2 get-apis --query 'Items[].ApiId' --output text | xargs -r -n1 aws apigatewayv2 delete-api --api-id
# Force delete log groups
aws logs describe-log-groups --query 'logGroups[].logGroupName' --output text | xargs -r -n1 aws logs delete-log-group --log-group-name
echo "π₯ Emergency cleanup complete!"
else
echo "β Emergency cleanup cancelled"
fi
Summary Checklist
- CDK Stacks Destroyed (
cdk destroy --all --force) - CloudFormation Stacks Deleted (Manual verification)
- Resources Cleaned Per Account (Lambda, API Gateway, Logs)
- Control Tower Landing Zone Deleted (Console or CLI)
- AWS Accounts Closed (6 created accounts)
- Management Account Cleaned (Config, CloudTrail, etc.)
- Local Project Removed (
rm -rf simple-control-tower-cdk) - Costs Verified ($0 across all accounts)
- Profiles Cleaned (Optional AWS CLI cleanup)
Total Estimated Time: 2-4 hours (including account closure waiting periods)
π° Final Cost Check: After 24-48 hours, verify $0 charges across all accounts.
β οΈ Remember: Account closure has a 90-day cooling period. You may see the accounts in a "suspended" state during this time, but they won't incur charges.