TL;DR: A fresh AWS account needs a great deal before it is safe to use: hardening, audit logging, threat detection, operator roles. The temptation is to make the first module you run do all of it. terraform-aws-bootstrap does the opposite. It does exactly three things, a remote state backend, a CI identity, and an account-scrub config, and deliberately nothing else. Everything else belongs in a later stack, applied through the very CI identity that bootstrap creates. A bootstrap module earns its keep by being narrow.

The first-apply problem

A brand-new AWS account is not ready for anything serious. Before you would responsibly run real infrastructure into it, you want an account baseline: a password policy, account-wide S3 public-access blocking, default EBS encryption, CloudTrail, AWS Config, GuardDuty, alerting, a sensible human operator role. It is a long list, and all of it matters.

The instinct, faced with that list, is to write one big “set up the account” module and have it do everything. One tofu apply, a fully prepared account, done.

That instinct is worth resisting, and terraform-aws-bootstrap resists it deliberately.

Three things, and a hard line

terraform-aws-bootstrap does three things:

  • state-backend — an S3 bucket and a customer-managed KMS key to hold remote Terraform state.
  • automation-iam — an OIDC identity provider and an IAM role that CI assumes to apply everything else.
  • nuke-config — it renders an aws-nuke configuration scoped to the account, for tearing a throwaway account back down.

That is the whole module. Account hardening, CloudTrail, AWS Config, GuardDuty, the operator role, the alerting: none of it is in here. And it is not absent by accident. The README has a section headed “what’s deliberately NOT in scope” that lists those exclusions out loud. The boundary is written down, because the boundary is the design.

Why the line is exactly there

The reason the line sits where it does is the most useful idea in the module.

Everything bootstrap excludes belongs in a separate stack, applied through the automation role bootstrap creates. Bootstrap’s only job is to get the account to the point where the next tofu apply can run properly: somewhere to store state, and an identity to run as. Once those two things exist, hardening the account is not a special bootstrapping act. It is just another apply, done the normal way: in CI, reviewed, versioned, deployed through the role.

So the account baseline does not need to be bundled into the bootstrap. It needs to be downstream of it. Bootstrap builds the on-ramp; it does not also have to be the motorway.

A narrow module stays re-runnable

There is a practical payoff to the narrowness, and it is about fear.

Bootstrap is the one stack that cannot be applied through CI, because it is what creates the CI identity in the first place. It runs locally, by a human, rarely. That is exactly the kind of operation you want to be small, boring, and safe to repeat.

A bootstrap module that also did account hardening would be a large, stateful thing managing dozens of resources. Re-running it would be a held-breath operation. Keeping it to three concerns keeps it the opposite: a small stack you can read top to bottom, re-run without anxiety, and reason about completely. The narrowness is not minimalism for its own sake. It is what keeps the one human-applied stack trustworthy.

The boundary is the feature

It is tempting to judge a module by how much it does. A bootstrap module is the case where that is exactly backwards. Its value is in how cleanly it stops.

terraform-aws-bootstrap does the bare minimum to make an account ready for the next apply, writes down everything it refuses to do, and hands off to a downstream stack for all of it. The next post follows the trickiest of its three jobs: the state backend has a genuine chicken-and-egg problem, because it has to store Terraform state in a bucket Terraform has not created yet.

Where this leaves us

A fresh AWS account needs a long list of things before it is safe, and the obvious move is one big module that does the lot. terraform-aws-bootstrap deliberately does only three: a state backend, a CI identity, and an account-scrub config. Everything else is written down as out of scope.

The boundary is the design. The excluded work belongs in a downstream stack applied through the CI role bootstrap creates, so hardening is just a normal reviewed apply rather than a bootstrapping special case. And keeping the one human-run, locally-applied stack small is what keeps it safe to re-run. A bootstrap module is judged by where it stops.