Last active
September 22, 2025 11:46
-
-
Save arya2004/46e12c29c110b30426f91883e4bde13c to your computer and use it in GitHub Desktop.
This Bash script force-deletes all AWS resources across all regions in safe order. It sweeps compute (EC2, Lambda, RDS, DynamoDB, EKS, ECS, ELB/ALB, Auto Scaling, etc.), storage (S3, ECR), messaging (SQS, SNS), CloudFormation stacks, and networking (NAT gateways, EIPs, ENIs, IGWs, route tables, subnets, non-default NACLs/SGs/DHCP options, VPCs).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -euo pipefail | |
| # ===== CONFIG ===== | |
| CONFIRM=${CONFIRM:-yes} | |
| PARALLEL_REGION_JOBS=${PARALLEL_REGION_JOBS:-1} | |
| RDS_SKIP_FINAL_SNAPSHOT=${RDS_SKIP_FINAL_SNAPSHOT:-true} | |
| # ===== HELPER FUNCS ===== | |
| confirm() { | |
| if [[ "${CONFIRM}" == "yes" ]]; then | |
| read -r -p "This will DELETE resources across ALL regions. Type 'NUKE' to continue: " ans | |
| [[ "$ans" == "NUKE" ]] || { echo "Aborted."; exit 1; } | |
| fi | |
| } | |
| aws_retry() { | |
| # usage: aws_retry <cmd...> | |
| local attempt=0 max=7 | |
| local backoff=1 | |
| while true; do | |
| if "$@"; then return 0; fi | |
| rc=$? | |
| attempt=$((attempt+1)) | |
| if (( attempt >= max )); then return $rc; fi | |
| sleep $backoff | |
| backoff=$((backoff*2)) | |
| done | |
| } | |
| wait_nat_deleted() { | |
| local region=$1 | |
| local nat_id=$2 | |
| # Poll until nat gateway state is deleted/failed | |
| while true; do | |
| state=$(aws ec2 describe-nat-gateways --region "$region" \ | |
| --nat-gateway-ids "$nat_id" --query "NatGateways[0].State" --output text 2>/dev/null || echo "NotFound") | |
| if [[ "$state" == "deleted" || "$state" == "NotFound" ]]; then | |
| echo "NAT $nat_id deleted." | |
| break | |
| fi | |
| if [[ "$state" == "failed" ]]; then | |
| echo "NAT $nat_id failed; treating as gone." | |
| break | |
| fi | |
| echo "Waiting for NAT $nat_id to delete (state=$state)..." | |
| sleep 10 | |
| done | |
| } | |
| wait_rds_deleted() { | |
| local region=$1 | |
| local db_id=$2 | |
| while true; do | |
| out=$(aws rds describe-db-instances --region "$region" --db-instance-identifier "$db_id" \ | |
| --query "DBInstances[0].DBInstanceStatus" --output text 2>/dev/null || true) | |
| if [[ -z "$out" || "$out" == "None" || "$out" == "deleted" || "$out" == "not found" ]]; then | |
| echo "RDS $db_id deleted." | |
| break | |
| fi | |
| echo "Waiting for RDS $db_id to delete (status=$out)..." | |
| sleep 15 | |
| done | |
| } | |
| delete_region() { | |
| local region="$1" | |
| echo "========== Cleaning region: $region ==========" | |
| # -------- Compute: EC2 / ASG / LT / LC -------- | |
| # Terminate EC2 | |
| instances=$(aws ec2 describe-instances --region "$region" \ | |
| --query "Reservations[].Instances[].InstanceId" --output text) | |
| if [[ -n "${instances:-}" ]]; then | |
| echo "Terminating EC2: $instances" | |
| aws_retry aws ec2 terminate-instances --instance-ids $instances --region "$region" | |
| aws ec2 wait instance-terminated --instance-ids $instances --region "$region" || true | |
| fi | |
| # Auto Scaling Groups (force delete) | |
| asgs=$(aws autoscaling describe-auto-scaling-groups --region "$region" \ | |
| --query "AutoScalingGroups[].AutoScalingGroupName" --output text || true) | |
| for asg in $asgs; do | |
| echo "Deleting ASG: $asg" | |
| aws_retry aws autoscaling delete-auto-scaling-group --force-delete \ | |
| --auto-scaling-group-name "$asg" --region "$region" || true | |
| done | |
| # Launch Configurations | |
| lcs=$(aws autoscaling describe-launch-configurations --region "$region" \ | |
| --query "LaunchConfigurations[].LaunchConfigurationName" --output text || true) | |
| for lc in $lcs; do | |
| echo "Deleting Launch Config: $lc" | |
| aws_retry aws autoscaling delete-launch-configuration \ | |
| --launch-configuration-name "$lc" --region "$region" || true | |
| done | |
| # Launch Templates (delete all versions + template) | |
| lts=$(aws ec2 describe-launch-templates --region "$region" \ | |
| --query "LaunchTemplates[].LaunchTemplateId" --output text || true) | |
| for lt in $lts; do | |
| vers=$(aws ec2 describe-launch-template-versions --region "$region" \ | |
| --launch-template-id "$lt" --query "LaunchTemplateVersions[].VersionNumber" --output text || true) | |
| if [[ -n "$vers" ]]; then | |
| for v in $vers; do | |
| aws_retry aws ec2 delete-launch-template-versions --region "$region" \ | |
| --launch-template-id "$lt" --versions "$v" || true | |
| done | |
| fi | |
| aws_retry aws ec2 delete-launch-template --region "$region" --launch-template-id "$lt" || true | |
| done | |
| # -------- Lambda -------- | |
| functions=$(aws lambda list-functions --region "$region" \ | |
| --query "Functions[].FunctionName" --output text || true) | |
| for fn in $functions; do | |
| echo "Deleting Lambda: $fn" | |
| aws_retry aws lambda delete-function --function-name "$fn" --region "$region" || true | |
| done | |
| # -------- EKS (delete nodegroups & fargate profiles first) -------- | |
| clusters=$(aws eks list-clusters --region "$region" --query "clusters[]" --output text || true) | |
| for c in $clusters; do | |
| # Nodegroups | |
| ngs=$(aws eks list-nodegroups --cluster-name "$c" --region "$region" \ | |
| --query "nodegroups[]" --output text || true) | |
| for ng in $ngs; do | |
| echo "Deleting EKS nodegroup $ng in $c" | |
| aws_retry aws eks delete-nodegroup --cluster-name "$c" --nodegroup-name "$ng" --region "$region" || true | |
| # No waiter; rely on eventual consistency | |
| done | |
| # Fargate profiles | |
| fps=$(aws eks list-fargate-profiles --cluster-name "$c" --region "$region" \ | |
| --query "fargateProfileNames[]" --output text || true) | |
| for fp in $fps; do | |
| echo "Deleting EKS fargate profile $fp in $c" | |
| aws_retry aws eks delete-fargate-profile --cluster-name "$c" --fargate-profile-name "$fp" --region "$region" || true | |
| done | |
| echo "Deleting EKS cluster: $c" | |
| aws_retry aws eks delete-cluster --name "$c" --region "$region" || true | |
| done | |
| # -------- ECS -------- | |
| clusters=$(aws ecs list-clusters --region "$region" --query "clusterArns[]" --output text || true) | |
| for cl in $clusters; do | |
| svcs=$(aws ecs list-services --cluster "$cl" --region "$region" --query "serviceArns[]" --output text || true) | |
| for s in $svcs; do | |
| echo "Deleting ECS service: $s" | |
| aws_retry aws ecs update-service --cluster "$cl" --service "$s" --desired-count 0 --region "$region" || true | |
| aws_retry aws ecs delete-service --cluster "$cl" --service "$s" --force --region "$region" || true | |
| done | |
| echo "Deleting ECS cluster: $cl" | |
| aws_retry aws ecs delete-cluster --cluster "$cl" --region "$region" || true | |
| done | |
| # -------- ELB/ALB/NLB (v2) + Target Groups -------- | |
| lbs=$(aws elbv2 describe-load-balancers --region "$region" --query "LoadBalancers[].LoadBalancerArn" --output text || true) | |
| for lb in $lbs; do | |
| echo "Deleting ELBv2: $lb" | |
| aws_retry aws elbv2 delete-load-balancer --load-balancer-arn "$lb" --region "$region" || true | |
| done | |
| tgs=$(aws elbv2 describe-target-groups --region "$region" --query "TargetGroups[].TargetGroupArn" --output text || true) | |
| for tg in $tgs; do | |
| echo "Deleting Target Group: $tg" | |
| aws_retry aws elbv2 delete-target-group --target-group-arn "$tg" --region "$region" || true | |
| done | |
| # Classic ELB | |
| clbs=$(aws elb describe-load-balancers --region "$region" --query "LoadBalancerDescriptions[].LoadBalancerName" --output text 2>/dev/null || true) | |
| for clb in $clbs; do | |
| echo "Deleting Classic ELB: $clb" | |
| aws_retry aws elb delete-load-balancer --load-balancer-name "$clb" --region "$region" || true | |
| done | |
| # -------- RDS (instances then subnet groups) -------- | |
| dbs=$(aws rds describe-db-instances --region "$region" --query "DBInstances[].DBInstanceIdentifier" --output text 2>/dev/null || true) | |
| for db in $dbs; do | |
| status=$(aws rds describe-db-instances --db-instance-identifier "$db" --region "$region" \ | |
| --query "DBInstances[0].DBInstanceStatus" --output text 2>/dev/null || echo "") | |
| if [[ "$status" == "deleting" ]]; then | |
| echo "Skipping RDS $db (already deleting)" | |
| else | |
| echo "Deleting RDS DB: $db" | |
| if [[ "${RDS_SKIP_FINAL_SNAPSHOT}" == "true" ]]; then | |
| aws_retry aws rds delete-db-instance --db-instance-identifier "$db" --skip-final-snapshot --region "$region" || true | |
| else | |
| snapshot="${db}-final-$(date +%s)" | |
| aws_retry aws rds delete-db-instance --db-instance-identifier "$db" --final-db-snapshot-identifier "$snapshot" --region "$region" || true | |
| fi | |
| fi | |
| done | |
| # Optionally wait for RDS deletions to finish (prevents subnet group dependency errors) | |
| for db in $dbs; do wait_rds_deleted "$region" "$db"; done | |
| # Subnet groups | |
| rdsgs=$(aws rds describe-db-subnet-groups --region "$region" --query "DBSubnetGroups[].DBSubnetGroupName" --output text 2>/dev/null || true) | |
| for g in $rdsgs; do | |
| echo "Deleting RDS Subnet Group: $g" | |
| aws_retry aws rds delete-db-subnet-group --db-subnet-group-name "$g" --region "$region" || true | |
| done | |
| # -------- DynamoDB -------- | |
| tables=$(aws dynamodb list-tables --region "$region" --query "TableNames[]" --output text || true) | |
| for table in $tables; do | |
| echo "Deleting DynamoDB table: $table" | |
| aws_retry aws dynamodb delete-table --table-name "$table" --region "$region" || true | |
| done | |
| # -------- SQS / SNS -------- | |
| queues=$(aws sqs list-queues --region "$region" --query "QueueUrls[]" --output text 2>/dev/null || true) | |
| for q in $queues; do | |
| echo "Deleting SQS queue: $q" | |
| aws_retry aws sqs delete-queue --queue-url "$q" --region "$region" || true | |
| done | |
| topics=$(aws sns list-topics --region "$region" --query "Topics[].TopicArn" --output text 2>/dev/null || true) | |
| for t in $topics; do | |
| echo "Deleting SNS topic: $t" | |
| aws_retry aws sns delete-topic --topic-arn "$t" --region "$region" || true | |
| done | |
| # -------- ECR -------- | |
| repos=$(aws ecr describe-repositories --region "$region" --query "repositories[].repositoryName" --output text 2>/dev/null || true) | |
| for r in $repos; do | |
| echo "Deleting ECR repo: $r" | |
| aws_retry aws ecr delete-repository --repository-name "$r" --force --region "$region" || true | |
| done | |
| # -------- CloudFormation (dangerous; deletes stacks) -------- | |
| stacks=$(aws cloudformation list-stacks --region "$region" \ | |
| --query "StackSummaries[?StackStatus=='CREATE_COMPLETE' || StackStatus=='UPDATE_COMPLETE' || StackStatus=='ROLLBACK_COMPLETE'].StackId" \ | |
| --output text 2>/dev/null || true) | |
| for s in $stacks; do | |
| echo "Deleting CloudFormation stack: $s" | |
| aws_retry aws cloudformation delete-stack --stack-name "$s" --region "$region" || true | |
| done | |
| # -------- VPC Endpoints -------- | |
| vpes=$(aws ec2 describe-vpc-endpoints --region "$region" --query "VpcEndpoints[].VpcEndpointId" --output text 2>/dev/null || true) | |
| for vpe in $vpes; do | |
| echo "Deleting VPC Endpoint: $vpe" | |
| aws_retry aws ec2 delete-vpc-endpoints --vpc-endpoint-ids "$vpe" --region "$region" || true | |
| done | |
| # -------- NAT Gateways (must go before ENIs) -------- | |
| nats=$(aws ec2 describe-nat-gateways --region "$region" --query "NatGateways[].NatGatewayId" --output text 2>/dev/null || true) | |
| for nat in $nats; do | |
| echo "Deleting NAT Gateway: $nat" | |
| aws_retry aws ec2 delete-nat-gateway --nat-gateway-id "$nat" --region "$region" || true | |
| done | |
| for nat in $nats; do wait_nat_deleted "$region" "$nat"; done | |
| # -------- Release Elastic IPs -------- | |
| eips=$(aws ec2 describe-addresses --region "$region" --query "Addresses[].AllocationId" --output text 2>/dev/null || true) | |
| for e in $eips; do | |
| echo "Releasing EIP: $e" | |
| aws_retry aws ec2 release-address --allocation-id "$e" --region "$region" || true | |
| done | |
| # -------- Detach/Delete Internet Gateways -------- | |
| igws=$(aws ec2 describe-internet-gateways --region "$region" --query "InternetGateways[].InternetGatewayId" --output text || true) | |
| for igw in $igws; do | |
| vpcs_attached=$(aws ec2 describe-internet-gateways --internet-gateway-ids "$igw" --region "$region" \ | |
| --query "InternetGateways[0].Attachments[].VpcId" --output text || true) | |
| for v in $vpcs_attached; do | |
| aws_retry aws ec2 detach-internet-gateway --internet-gateway-id "$igw" --vpc-id "$v" --region "$region" || true | |
| done | |
| aws_retry aws ec2 delete-internet-gateway --internet-gateway-id "$igw" --region "$region" || true | |
| done | |
| # -------- Delete custom route tables (non-main) -------- | |
| rtbs=$(aws ec2 describe-route-tables --region "$region" \ | |
| --query "RouteTables[?Associations[?Main==\`false\`]][].RouteTableId" --output text || true) | |
| for rtb in $rtbs; do | |
| echo "Deleting Route Table: $rtb" | |
| aws_retry aws ec2 delete-route-table --route-table-id "$rtb" --region "$region" || true | |
| done | |
| # -------- Subnets -------- | |
| subnets=$(aws ec2 describe-subnets --region "$region" --query "Subnets[].SubnetId" --output text || true) | |
| for s in $subnets; do | |
| echo "Deleting Subnet: $s" | |
| aws_retry aws ec2 delete-subnet --subnet-id "$s" --region "$region" || true | |
| done | |
| # -------- Non-default NACLs -------- | |
| acls=$(aws ec2 describe-network-acls --region "$region" \ | |
| --query "NetworkAcls[?IsDefault==\`false\`].NetworkAclId" --output text || true) | |
| for a in $acls; do | |
| echo "Deleting NACL: $a" | |
| aws_retry aws ec2 delete-network-acl --network-acl-id "$a" --region "$region" || true | |
| done | |
| # -------- Non-default Security Groups -------- | |
| sgs=$(aws ec2 describe-security-groups --region "$region" \ | |
| --query "SecurityGroups[?GroupName!='default'].GroupId" --output text || true) | |
| for sg in $sgs; do | |
| echo "Deleting Security Group: $sg" | |
| aws_retry aws ec2 delete-security-group --group-id "$sg" --region "$region" || true | |
| done | |
| # -------- Non-default DHCP Options -------- | |
| dopt=$(aws ec2 describe-dhcp-options --region "$region" \ | |
| --query "DhcpOptions[?DhcpOptionsId!='dopt-xxxxxxxx'].DhcpOptionsId" --output text || true) | |
| # The above query returns all; we’ll filter out those currently associated to some VPC | |
| for d in $dopt; do | |
| # Skip if associated | |
| assoc=$(aws ec2 describe-vpcs --region "$region" --filters "Name=dhcp-options-id,Values=$d" \ | |
| --query "Vpcs[].VpcId" --output text || true) | |
| if [[ -z "$assoc" ]]; then | |
| echo "Deleting DHCP Options: $d" | |
| aws_retry aws ec2 delete-dhcp-options --dhcp-options-id "$d" --region "$region" || true | |
| fi | |
| done | |
| # -------- ENIs (best-effort; most will be gone now) -------- | |
| enis=$(aws ec2 describe-network-interfaces --region "$region" \ | |
| --query "NetworkInterfaces[?Status=='available'].NetworkInterfaceId" --output text || true) | |
| for eni in $enis; do | |
| echo "Deleting ENI: $eni" | |
| aws_retry aws ec2 delete-network-interface --network-interface-id "$eni" --region "$region" || true | |
| done | |
| # -------- Non-default VPCs -------- | |
| vpcs=$(aws ec2 describe-vpcs --region "$region" \ | |
| --query "Vpcs[?IsDefault==\`false\`].VpcId" --output text || true) | |
| for v in $vpcs; do | |
| echo "Deleting VPC: $v" | |
| aws_retry aws ec2 delete-vpc --vpc-id "$v" --region "$region" || true | |
| done | |
| echo "Region $region done." | |
| } | |
| # ===== MAIN ===== | |
| confirm | |
| echo "Fetching all AWS regions" | |
| regions=$(aws ec2 describe-regions --query "Regions[].RegionName" --output text) | |
| # S3 (global) first — buckets often block stack cleanups elsewhere | |
| echo "========== Cleaning S3 buckets (global) ==========" | |
| buckets=$(aws s3api list-buckets --query "Buckets[].Name" --output text || true) | |
| for b in $buckets; do | |
| echo "Deleting S3 bucket: $b" | |
| aws_retry aws s3 rb "s3://$b" --force || true | |
| done | |
| # Optionally run per-region sequentially or in parallel | |
| if (( PARALLEL_REGION_JOBS > 1 )); then | |
| # Parallel execution (limited) | |
| export -f aws_retry wait_nat_deleted wait_rds_deleted delete_region | |
| echo "$regions" | xargs -n1 -P "$PARALLEL_REGION_JOBS" -I{} bash -c 'delete_region "$@"' _ {} | |
| else | |
| for region in $regions; do | |
| delete_region "$region" | |
| done | |
| fi | |
| echo "========== Cleanup Complete ==========" | |
| echo "Tip: run 'aws resourcegroupstaggingapi get-resources --region <r>' to verify emptiness." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment