Skip to main content

Pulumi Ep 2: Stacks and State Management

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
pulumi - This article is part of a series.
Part 2: This Article
If you survived the Terraform series, you know the pain of configuring a remote backend (s3, dynamodb_table). Pulumi takes a radically different approach: it provides a fully managed SaaS platform (the Pulumi Service) that handles state, locking, and history automatically by default. Let’s explore how it works.

1. Where is the State File?
#

In the previous episode, you ran pulumi up and created an S3 bucket. If you look at your local directory, you will notice something surprising: there is no state file.

By default, when you run pulumi new, the CLI automatically logs you into the Pulumi Service (app.pulumi.com). This is the default backend.

When you run pulumi up, the CLI sends the state file directly to the Pulumi Service over an encrypted connection.

The Benefits of the Pulumi Service
#

  1. Zero Configuration: No need to write 50 lines of boilerplate to bootstrap an AWS S3 bucket and DynamoDB table.
  2. Native Concurrency Locking: If two developers run pulumi up at the same time, the Service instantly rejects the second request.
  3. Deep History: The Web UI tracks every single deployment, who executed it, how long it took, and exact diffs of the code changes.
  4. Secret Management: Passwords and API keys are automatically encrypted using a KMS key managed by Pulumi before they are stored in the state file.
Note

If your company’s compliance rules prohibit sending state data to a third-party SaaS, Pulumi natively supports storing state in your own AWS S3 bucket. You can switch backends by running pulumi login s3://my-company-bucket.


2. The Concept of Stacks
#

In Terraform, separating environments (Staging vs Production) requires complex Directory Isolation architectures (as discussed in Terraform Ep 11).

In Pulumi, environment separation is a first-class citizen built directly into the CLI via Stacks.

A Stack is an isolated, independently configurable instance of your Pulumi program. Most teams map Stacks directly to environments. For example, you have one codebase, but three stacks: dev, staging, and prod.

Managing Stacks via CLI
#

Let’s list the current stacks in our project:

pulumi stack ls

Expected Terminal Output:

NAME  LAST UPDATE     RESOURCE COUNT  URL
dev*  10 minutes ago  2               https://app.pulumi.com/rhidayat/...

(The asterisk * indicates this is your currently active stack).

Let’s create a new stack for production:

pulumi stack init prod

If you run pulumi stack ls again, the asterisk is now next to prod.

pulumi up

Because this is a brand new stack, Pulumi starts from an empty state file. It will create a second, completely distinct S3 bucket in your AWS account. You now have two isolated environments running from the exact same TypeScript file!


3. Auto-Naming (Collision Prevention)
#

When we created the second bucket for the prod stack, you might have wondered: AWS requires S3 bucket names to be globally unique. If both stacks use the exact same TypeScript code, won’t the bucket names collide?

Let’s look at our code from Episode 1:

const bucket = new aws.s3.Bucket("my-bucket");

You might think "my-bucket" is the physical name sent to AWS. It is not.

"my-bucket" is the Logical Name used by Pulumi to track the resource in the state file.

By default, Pulumi utilizes a feature called Auto-Naming. When the engine communicates with AWS, it automatically appends a random hex suffix to the logical name to generate the physical name.

  • In the dev stack, the bucket is named: my-bucket-1a2b3c4
  • In the prod stack, the bucket is named: my-bucket-9f8e7d6

Overriding Auto-Naming
#

If you absolutely must specify an exact, strict physical name (for compliance reasons or legacy integrations), you can override Auto-Naming by passing the specific property required by the cloud provider (in AWS S3, this is the bucket property).

const exactBucket = new aws.s3.Bucket("my-logical-name", {
    // Overriding auto-naming!
    bucket: "my-company-exact-bucket-name-prod",
});
Warning

If you override Auto-Naming and try to deploy this code to two different Stacks, the second stack will fail with a BucketAlreadyExists API error from AWS. Only override Auto-Naming when absolutely necessary.


4. Stack Teardown
#

If you are experimenting, you should clean up your resources to avoid AWS charges.

Ensure you are on the dev stack:

pulumi stack select dev

Run the destroy command:

pulumi destroy

This acts exactly like terraform destroy. It reads the state file, determines the dependency graph in reverse, and deletes the physical cloud resources.

However, the Stack itself (and its configuration history) still exists in the Pulumi Service. To completely obliterate the Stack from existence:

pulumi stack rm dev

Troubleshooting & Common Errors
#

  1. error: the current stack has a pending operation

    • Root Cause: A previous pulumi up command crashed, lost internet connection, or was killed via Ctrl+C before it finished. Pulumi locked the state file to prevent corruption.
    • Solution: Run pulumi cancel to force the Pulumi Service to release the lock. (Be warned: your actual AWS infrastructure may be in a partially deployed state).
  2. error: failed to load checkpoint

    • Root Cause: You logged into a different backend (e.g., local file system or S3) but your current directory still thinks it is connected to the Pulumi Service.
    • Solution: Verify your active backend by running pulumi whoami -v.

Conclusion & Next Steps
#

You now understand how Pulumi simplifies state management through its managed SaaS backend, and how Stacks allow you to effortlessly deploy isolated environments without duplicating code.

But our TypeScript code is currently extremely basic. In Episode 3: Declaring Resources in TypeScript, we will dive deep into the object-oriented nature of Pulumi. We will learn how to read the documentation, pass complex arguments via Interfaces, and map AWS API properties to TypeScript types.

pulumi - This article is part of a series.
Part 2: This Article