The first time I pointed aws-nuke at a real account, the dry-run printed hundreds of lines of angry red text and my stomach dropped. Then I read it properly, and two things turned out to be true at once. Almost all of that red was noise. And the one operation I genuinely should have worried about wasn’t red at all.
A tool whose whole job is destruction
aws-nuke deletes everything in an AWS account. That’s the point of it: when you spin up a throwaway account to try something, aws-nuke is how you tear it back down to nothing afterwards rather than leaving resources quietly billing forever. go-tool-base’s bootstrap renders a scoped aws-nuke config for exactly this, from a nuke-config module, so the teardown is described in code rather than typed by hand at the worst possible moment.
A tool that deletes everything is a tool you run in dry-run first, every single time, and read the output before you let it touch anything. So that’s what I did. And the output was alarming in a way that turned out to be completely meaningless, and reassuring in a way that turned out to hide the one real hazard.
The wall of red that means nothing
A fresh account threw up screen after screen of SubscriptionRequiredException. Hundreds of lines, all red, all looking like something had gone badly wrong.
They hadn’t. aws-nuke works by asking every region “do you have any of this kind of resource?”, for every kind of resource it knows about. On a brand-new account you’ve never enabled most services in most regions, so the API’s honest answer is “you’re not subscribed to that here”, which surfaces as an exception, which the tool dutifully logs in red. It isn’t a failure. It’s the sound of an empty account being asked four hundred questions and answering “nothing here” to almost all of them.
The skill, and it is a skill, is learning to read a destructive tool’s dry-run and tell the noise from the signal. SubscriptionRequiredException on a fresh account is noise. Once you know that, the wall of red stops being frightening and becomes scenery.
There’s a related trap in the same neighbourhood, and the nuke-config module’s own regions variable documents it. aws-nuke has a special global pseudo-region for things that don’t live in one place (IAM, Route 53, CloudFront), and then the actual regions for everything else. It also accepts all, meaning every enabled region. Mixing all with explicit region values scans some regions twice and muddies the output, so the module’s guidance is to pick one approach or the other. More scenery you have to learn to read before the genuinely important line will stand out.
The line that should have scared me, and didn’t look like it
Buried in that calm-looking eye of the storm, among the resources aws-nuke intended to delete, were the IAM resources granting the identity running the nuke its administrative access.
Sit with that for a second. aws-nuke runs as some principal with enough power to delete everything. To delete EVERYTHING, it has to delete IAM resources too. And if the plan deletes the very grant that gives the running identity its admin before it’s finished, the tool strands itself partway through: no permissions left to complete the teardown, and now you’ve got a half-nuked account and a principal that can’t act on it. The cleanup tool sawing off the branch it’s standing on, calmly, without a single red line to warn you, because from the API’s point of view deleting that resource is a perfectly valid request.
That’s the operation that actually mattered, and it was the quietest thing in the output.
Two ways to keep its hands attached
The fix has two halves, one explicit and one structural.
The explicit half is to preserve the privilege path. The nuke-config module passes a caller-supplied set of filters straight through into the rendered config, so you tell aws-nuke “everything except these”. You exclude the identity running the nuke, and the policy and path that grant it admin, from deletion. The tool cleans the account and leaves its own hands alone, because you told it which resources are off-limits.
The structural half is to not give it a tempting separate thing to delete in the first place. If an identity gets its admin through an IAM group it belongs to, that group is its own deletable resource, one more thing in the plan, one more way to be stranded. The automation role in terraform-aws-bootstrap instead takes its policies as direct attachments to the role itself:
resource "aws_iam_role_policy_attachment" "gitlab" {
for_each = local.is_gitlab ? var.policy_arns : {}
role = aws_iam_role.this.name
policy_arn = each.value
}
No intermediary group sitting there as a separate, deletable object. Flattening the privilege onto the role makes the dependency simpler to reason about and gives the cleanup tool one fewer foot-gun to find. Belt and braces: filter the path out explicitly, and don’t build a structure that invites the problem.
What it comes down to
A destructive tool’s dry-run is the most valuable thing it produces, and reading it well is its own competence. On a fresh account, the screenfuls of red SubscriptionRequiredException are noise, the sound of an empty account answering “nothing here”, and the all-versus-global region wrinkle is more of the same. Learn to see past all of it, because the operation that can actually hurt you is rarely the one shouting. Mine was the calm, unremarkable line proposing to delete the admin grant the nuke needed to finish its own job.
Keep the cleanup tool’s hands attached: filter the privileged path out of the teardown so it’s never a candidate for deletion, and attach that privilege directly rather than through a group that’s just one more thing to delete. Then let it loose on everything else, which is, after all, what you brought it in to do.
