all blogs
infrastructure

Managing infrastructure with Pulumi and S3: a journey into code-driven IaC

Why self-managing Pulumi state in a versioned S3 bucket beats the default backends when control and compliance matter.

Pulumi with a self-managed S3 state backend. On the left, infrastructure written as Python declares two AWS providers pinned to different regions. Running pulumi up writes state to an S3 bucket configured with pulumi login, with versioning enabled for rollback plus KMS encryption and CloudTrail. From that single codebase, one primary region and one recovery region are deployed. Along the bottom, four benefits: data residency, rollback through object versions, audit via CloudTrail, and avoiding concurrent writes by deploying through CI only.
One codebase, two providers, and a state file that never leaves an account you control.

01 The short version

Pulumi has to keep infrastructure state somewhere, and both defaults come with constraints: Pulumi Cloud raises questions about data residency and compliance, while local storage doesn't survive contact with a team.

The alternative is self-managing state in a versioned S3 bucket. `pulumi login s3://bucket-name` switches the backend, and from there you deploy infrastructure written as ordinary Python, with the state file somewhere you control end to end.

Most of the piece is the operational detail that makes it safe: IAM policies scoped to the bucket, KMS encryption for production, CloudTrail for auditing, and preventing simultaneous deploys from corrupting state.

The pattern I keep returning to is configuring several AWS providers with different regions inside one codebase, which turns multi-region disaster recovery into provider configuration rather than duplicated stacks.

02What you'll take away

  • Switching backends is a login command — it doesn't touch your resource definitions at all.
  • Bucket versioning is what buys you rollback: state history is preserved, so a previous infrastructure state can be recovered.
  • Multiple provider objects, each pinned to a region, let a single codebase deploy a primary and its recovery targets.
  • Auditing and locking aren't optional on a team: CloudTrail for who-changed-what, and deploy-through-CI-only to avoid concurrent state writes.