Pulumi improved on Terraform in a real way by letting teams write infrastructure in TypeScript, Python, or Go instead of HCL. You get full IDE support, type checking, and the ability to use loops and conditionals that actually work the way you'd expect. For a lot of teams, that's been enough.
The friction is in what didn't change. You're still writing infrastructure configuration in a separate program from your application code, and the state file still exists whether you manage it yourself or pay for Pulumi Cloud. Pulumi Cloud's pricing scales with the number of resources you manage, which gets expensive as infrastructure grows. Teams that adopted Pulumi to escape Terraform's limitations sometimes find the operational overhead is similar, just with better syntax.
Most Pulumi alternatives are other IaC tools with different tradeoffs on language, cloud support, or state management. This guide covers those, plus the approach that eliminates separate infrastructure configuration and state files entirely.
| Feature | Encore | Terraform | OpenTofu | AWS CDK | SST | Crossplane |
|---|---|---|---|---|---|---|
| Approach | Infrastructure from code | IaC (HCL) | IaC (HCL) | IaC (constructs) | IaC (serverless-first) | IaC (Kubernetes-native) |
| Language | TypeScript / Go | HCL | HCL | TypeScript, Python, Go, Java, C# | TypeScript | YAML (K8s manifests) |
| State management | None (no state file) | Remote state file | Remote state file | CloudFormation stacks | Pulumi (managed or self-hosted) | Kubernetes etcd |
| Cloud support | AWS, GCP (provisions in your account) | AWS, GCP, Azure, 6,000+ providers | AWS, GCP, Azure, 6,000+ providers | AWS only | AWS only | AWS, GCP, Azure via providers |
| Infrastructure config | Zero (derived from app code) | Separate HCL files | Separate HCL files | Separate program files | Separate config files | Separate YAML manifests |
| Learning curve | Low (just TypeScript) | Medium (HCL) | Medium (HCL) | Medium (CDK constructs) | Medium (Pulumi under the hood) | High (Kubernetes + CRDs) |
| AI agent support | MCP server, infra in same code agents read | Limited | Limited | Limited | Limited | None |
| Vendor lock-in | Low (eject to Docker anytime) | Low | None | High (AWS only) | High (AWS only) | Moderate (Kubernetes) |
| Best for | Zero infra config, no state files, one pipeline | Multi-cloud IaC with huge ecosystem | Same as Terraform, open-source license | AWS-only shops | Serverless on AWS | Kubernetes platform teams |
Encore takes a different approach from Pulumi and every other IaC tool. Instead of writing infrastructure in a separate TypeScript program, you declare what your application needs directly in your application code. Databases, Pub/Sub topics, cron jobs, object storage, and caching are declared as objects in the same files where you use them. Encore's open-source framework parses the code to understand infrastructure requirements, and Encore Cloud provisions the corresponding AWS or GCP resources in your own account.
Where Pulumi asks you to write a separate index.ts that defines your RDS instance, subnet group, security group, and IAM role, Encore asks you to write new SQLDatabase("orders", { migrations: "./migrations" }) in the file where you query that database. The infrastructure follows from the application code, without a state file or a separate deployment pipeline for infrastructure.
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { Topic } from "encore.dev/pubsub";
import { CronJob } from "encore.dev/cron";
// Provisions RDS on AWS or Cloud SQL on GCP with sensible defaults, fine-tunable later.
const db = new SQLDatabase("orders", { migrations: "./migrations" });
// Provisions SNS+SQS on AWS or GCP Pub/Sub on GCP with sensible defaults, fine-tunable later.
const orderEvents = new Topic<OrderEvent>("order-events", {
deliveryGuarantee: "at-least-once",
});
export const createOrder = api(
{ method: "POST", path: "/orders", expose: true, auth: true },
async (req: CreateOrderRequest): Promise<Order> => {
const order = await db.queryRow`
INSERT INTO orders (customer_id, total)
VALUES (${req.customerId}, ${req.total})
RETURNING *`;
await orderEvents.publish({ orderId: order!.id, total: order!.total });
return order!;
}
);
// Provisions CloudWatch Events on AWS or Cloud Scheduler on GCP.
const cleanup = new CronJob("daily-cleanup", {
title: "Clean up expired orders",
schedule: "0 2 * * *",
endpoint: cleanupExpiredOrders,
});
Compare this to the equivalent Pulumi code, which requires separate resource definitions for the VPC, subnets, security groups, RDS instance, SNS topic, SQS queue, subscription, IAM roles, and Fargate service. A side-by-side comparison puts the Pulumi version at roughly 200 lines of infrastructure TypeScript for the same setup.
You still get configuration control over instance types, database sizes, regions, and scaling parameters through Encore Cloud, and sensible production defaults are applied automatically so you only configure what you actually want to override.
Pulumi made the authoring experience better, but you're still maintaining a separate infrastructure program that needs its own reviews, its own pipeline, and its own state backend. With Encore, infrastructure changes happen in the same commit as application changes, reviewed by the same person, deployed through one pipeline. Preview environments get the right infrastructure automatically because Encore reads the code.
Billing is also simpler. Pulumi Cloud charges per resource managed, which scales with your infrastructure. Encore Cloud charges a flat platform fee, and your AWS or GCP resources are billed at standard cloud provider rates directly to your account.
encore build docker for standard Docker imagesAI coding tools struggle with Pulumi for the same reason they struggle with Terraform: there are many ways to configure the same thing and the agent has to make infrastructure decisions it's not well-equipped to make. With Encore, an agent declares a database or a Pub/Sub topic with one line of code and the framework handles the rest with sensible production defaults. The agent doesn't need to know about instance sizes, subnet groups, or IAM policies because Encore applies those defaults automatically. If you need to override something later, you can, but the generated code is deployable from the start.
Because infrastructure lives in the same files as the application code, an agent can also see the full picture in one place: the database, the topics, the API endpoints. Encore provides an MCP server and editor rules that give agents access to database schemas, distributed traces, and the full service architecture.
Encore supports TypeScript and Go, and provisions infrastructure on AWS and GCP. If you need Azure, or if your infrastructure requirements go beyond what Encore's primitives cover (custom VPC topologies, specialized AWS services), you'll still need Pulumi or another IaC tool for those parts. Encore handles the common 80% of backend infrastructure and coexists with existing IaC tools for the rest.
Want to jump straight to a running app? Clone this starter and deploy it to your own cloud.
See the quick start guide to get started, or book a 1:1 intro for a walkthrough.
Terraform is the tool Pulumi was designed to replace, and yet some teams go back. If your frustration with Pulumi is about pricing or the complexity of Pulumi's SDK, Terraform's ecosystem is larger, the community knowledge base is deeper, and the learning resources are more abundant. HCL is less expressive than TypeScript, but it's also more predictable. What you write is what you get.
Terraform's provider ecosystem covers 6,000+ providers through the registry, far beyond what any other tool offers. For teams managing infrastructure across multiple clouds and SaaS providers, that breadth is hard to match.
resource "aws_db_instance" "orders" {
identifier = "orders"
engine = "postgres"
engine_version = "16"
instance_class = "db.t4g.micro"
db_name = "orders"
username = "app"
password = var.db_password
}
Going from Pulumi back to Terraform means giving up real programming languages for HCL, which is a meaningful step backward in expressiveness. State management, the two-codebase problem, and the coordination overhead between infrastructure and application changes all remain. IBM's acquisition of HashiCorp and the BSL license change are also worth considering if licensing matters to your organization.
OpenTofu is the open-source fork of Terraform maintained by the Linux Foundation. If you're considering Terraform but the BSL license is a concern, OpenTofu gives you the same HCL, the same providers, and the same workflow under MPL-2.0. Your existing Terraform files work without changes.
OpenTofu has added features Terraform doesn't have: built-in state file encryption, early variable evaluation for dynamic provider configuration, and for_each on provider blocks. The provider ecosystem is fully compatible.
OpenTofu solves the licensing concern with Terraform. It doesn't change the fundamental model. You're still writing HCL, managing state files, and maintaining infrastructure configuration separately from your application code. If your frustration with Pulumi was about the IaC model itself rather than the specific language, OpenTofu has the same structural limitations.
AWS CDK lets you define AWS infrastructure in TypeScript, Python, Go, Java, or C#. Like Pulumi, you get real programming languages with type safety and IDE support. The key difference is that CDK synthesizes to CloudFormation templates, so the deployment model is CloudFormation's stack-based approach rather than Pulumi's graph-based state.
CDK's high-level constructs bundle common patterns. A single ApplicationLoadBalancedFargateService creates a Fargate service, load balancer, target group, security groups, and IAM roles together.
import * as cdk from "aws-cdk-lib";
import * as rds from "aws-cdk-lib/aws-rds";
import * as ec2 from "aws-cdk-lib/aws-ec2";
const vpc = new ec2.Vpc(this, "VPC");
const db = new rds.DatabaseInstance(this, "Orders", {
engine: rds.DatabaseInstanceEngine.postgres({
version: rds.PostgresEngineVersion.VER_16,
}),
vpc,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T4G, ec2.InstanceSize.MICRO
),
databaseName: "orders",
});
CDK is AWS only. If you deploy to GCP or Azure, CDK doesn't help, and that's a step backward from Pulumi's multi-cloud support. CloudFormation's deployment model has its own limitations: stack size limits, slow rollbacks on failure, and occasional stuck resources. CDK also still requires maintaining infrastructure code separately from application code, same as Pulumi.
SST is a framework for building and deploying applications on AWS. SST v3 (Ion) replaced CDK with Pulumi under the hood, which means SST is less a Pulumi alternative and more a higher-level layer on top of it. If your frustration with Pulumi is about the amount of boilerplate you write rather than the model itself, SST's components reduce that significantly.
SST's standout features are live Lambda development with breakpoint debugging and its Console for inspecting deployed resources.
// sst.config.ts
export default $config({
app(input) {
return { name: "orders", home: "aws" };
},
async run() {
const db = new sst.aws.Postgres("OrdersDB", { scaling: { min: "0.5 ACU", max: "2 ACU" } });
const api = new sst.aws.Function("API", {
handler: "src/api.handler",
link: [db],
url: true,
});
return { url: api.url };
},
});
SST uses Pulumi under the hood, so you're not escaping Pulumi's state management or deployment model. You still need a Pulumi state backend. SST is AWS-only and primarily designed for serverless workloads. If you're leaving Pulumi because of the IaC model, SST doesn't change that. If you're leaving because Pulumi's SDK is too low-level for AWS work, SST is a reasonable step up.
Crossplane manages cloud infrastructure through Kubernetes custom resources. You define infrastructure as Kubernetes manifests and Crossplane controllers reconcile those manifests against your cloud provider. If your team has standardized on Kubernetes and wants to manage all infrastructure through kubectl and GitOps workflows, Crossplane fits that model.
Crossplane requires a running Kubernetes cluster, which is a significant prerequisite. The YAML manifests are verbose, provider coverage varies, and debugging reconciliation failures requires understanding both Kubernetes and cloud provider APIs. Moving from Pulumi's TypeScript to Crossplane's YAML is a step backward in developer experience. Crossplane makes sense for platform teams that have bet on Kubernetes as their control plane. For application developers, it adds complexity.
Teams leaving Pulumi are usually frustrated by one of three things: the pricing, the operational overhead of state management, or the realization that writing infrastructure in TypeScript is still writing infrastructure.
If the pricing is the issue but the IaC model works for you, Terraform or OpenTofu get you back to a tool with no per-resource fees. You give up TypeScript for HCL, but the ecosystem is larger and the community support is deeper. OpenTofu adds the benefit of open-source governance.
If you want to stay in TypeScript on AWS but want less boilerplate, SST is a higher-level layer on top of Pulumi that reduces the configuration you write. AWS CDK is the alternative if you prefer CloudFormation's deployment model over Pulumi's.
If Kubernetes is your control plane for everything, Crossplane extends that model to cloud infrastructure. It makes sense for platform teams, not application developers.
If the IaC model itself is the frustration, with state files, separate infrastructure programs, and the coordination overhead between infrastructure and application changes, every tool on this list except Encore still requires that. Pulumi made the authoring experience better by using real languages, but the operational model is the same.
Encore is the only tool here that eliminates infrastructure configuration entirely. Your application code declares what it needs, and the platform provisions it in your AWS or GCP account. No state files, no separate programs, no plan/apply cycle. For teams building TypeScript or Go backends who want to stop maintaining infrastructure alongside their application, Encore is the most direct path forward.
Want to jump straight to a running app? Clone this starter and deploy it to your own cloud.