What Pulumi is and the idea behind it
Pulumi starts from a simple proposition: infrastructure is software, so write it in a software language. A Pulumi program in TypeScript, Python, Go, C# or Java declares resources as objects. When you run a preview, the engine builds a resource graph from your program, compares it with recorded state, and shows what would change. An update then applies it. The workflow will feel familiar to anyone who has used Terraform; the authoring experience is what differs.
The benefit is real for the right team. You get functions, classes, package managers, type checking, IDE completion and ordinary unit tests. A platform team can publish a typed component for “a production database” and application teams call it like any library.
We deliver Pulumi work as part of our wider infrastructure as code practice, applying the same discipline we use elsewhere: reviewed changes, small blast radius and a pipeline that does the applying.
A short example
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const env = pulumi.getStack();
const logs = new aws.s3.BucketV2(`logs-${env}`, {
forceDestroy: false,
tags: { environment: env, managedBy: "pulumi" },
});
new aws.s3.BucketPublicAccessBlock(`logs-block-${env}`, {
bucket: logs.id,
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true,
});
export const bucketName = logs.bucket;
The same shape in a loop over environments or tenants is where a general-purpose language starts to save effort, and also where reviewers must pay attention.
How we structure Pulumi in production
Projects and stacks
A project is a program; a stack is one instance of it with its own configuration and state, typically one per environment. We keep stacks small and split projects by lifecycle and ownership: networking, data, cluster, applications. Stack references or outputs pass values between them. As with any tool, a single oversized stack slows updates and increases risk.
Component resources
Components are Pulumi’s way of encapsulating a pattern. A good component takes a small typed input, creates several underlying resources and exposes a few outputs. We publish them as internal packages with versioning and changelogs, so consumers upgrade on purpose. Over-abstraction is the common mistake: a component that tries to cover every case becomes harder to read than the raw resources.
Naming deserves attention too. Pulumi derives physical resource names from logical names plus a random suffix by default, which avoids collisions but can surprise people searching the console. We set explicit names only where an external system depends on them, since fixed names make replacement operations harder.
Testing
Because it is code, you can test it. Unit tests with mocks run quickly and check logic such as tagging rules and naming. Integration tests deploy to a scratch environment and are slower and costlier, so we use them sparingly for important components. Tests do not replace a reviewed preview; they complement it.
State, secrets and access
State holds resource identifiers and encrypted secrets. Pulumi Cloud offers hosted state, access control and an audit trail. A self-managed backend in object storage avoids a third-party service but moves access control, locking behaviour and operations to you. We choose with you based on compliance needs and team capacity, and we explain the trade-off in plain terms.
Secrets are encrypted in config and state using a secrets provider, which can be Pulumi’s service or a cloud key management service. For wider secret handling, Pulumi ESC or your existing secrets manager keeps values out of repositories. Pipelines authenticate to clouds with short-lived federated credentials rather than stored keys, and separate preview from update permissions where the platform supports it.
Policy as code (Pulumi policy packs) lets you block non-compliant resources at preview time, such as public buckets or untagged resources.
Cost and capacity
The open source engine is free. Hosted features may carry pricing that depends on your plan and resource counts, which you should check against current terms before committing. We do not state prices. Beyond licensing, the main costs are engineering time and the discipline needed to keep programs readable. Update speed depends on resource count and provider rate limits, so smaller stacks help.
Day-2 operations
- Upgrades. SDKs, providers and the CLI move independently. Pin versions, upgrade in lower environments and read provider changelogs.
- Drift. Run refresh previews on a schedule to compare state with reality, and reconcile deliberately.
- Stack hygiene. Remove unused stacks, protect critical resources with the
protectoption and review who has access. - Dependency management. Treat the language’s packages as part of your supply chain, with lock files and vulnerability scanning.
Common failure modes
- Clever code. Dynamic constructs that obscure what will be created. Keep the preview readable.
- Language sprawl. Three languages across teams makes shared components impossible.
- Replace surprises. Renaming a logical resource can replace the physical one. Use aliases and read previews.
- Unmanaged secrets. Config values committed in plain text or leaked through outputs.
- State loss. A self-managed backend without versioning or backup.
When Pulumi is the wrong choice
If your team is mostly operators who prefer a compact declarative format, Terraform is usually easier to hire for and review. If you are AWS-only and want AWS to hold state, CloudFormation is simpler to operate. If you need many teams to contribute small changes with minimal training, a restrictive language is an advantage, not a limitation. And if your main task is server configuration, Ansible is the better layer.
Moving between tools is possible but never free, so it is worth deciding properly. Our infrastructure as code service and a short consultation can help you weigh it.
Official documentation: www.pulumi.com/docs