Skip to content

Instantly share code, notes, and snippets.

@arya2004
Last active September 22, 2025 11:46
Show Gist options
  • Select an option

  • Save arya2004/46e12c29c110b30426f91883e4bde13c to your computer and use it in GitHub Desktop.

Select an option

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).
#!/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