Skip to content

Explanation

About this page

How Pulumi works internally and why it is designed that way: the engine, language hosts, providers, the Terraform bridge, state and journaling, the Pulumi Cloud services (ESC, Neo, IDP, Discovery & Governance), and the security model. Commands live in How-to Guides; versions, prices, limits and checklists live in Reference. Hub: Pulumi.

Pulumi is an infrastructure-as-code platform where you describe cloud resources in a general-purpose language (TypeScript/JavaScript, Python, Go, .NET, Java) or in Pulumi YAML or, since 2026, HCL. The program does not call cloud APIs itself. It registers desired resources with the Pulumi engine. The engine diffs the registrations against the last recorded state and drives provider plugins to converge real infrastructure.


Component Overview

This diagram shows the processes that take part in a pulumi up. Every arrow marked gRPC crosses a process boundary.

graph TB
    subgraph USER["User program"]
        TS["TypeScript / JavaScript"]
        PY["Python"]
        GO["Go"]
        CS[".NET (C#, F#, VB)"]
        JA["Java"]
        YH["Pulumi YAML / HCL"]
    end

    subgraph LH["Language host plugins"]
        LHN["pulumi-language-nodejs<br/>(Node.js or Bun)"]
        LHP["pulumi-language-python"]
        LHG["pulumi-language-go"]
        LHD["pulumi-language-dotnet"]
        LHJ["pulumi-language-java"]
        LHY["pulumi-language-yaml / hcl"]
    end

    subgraph ENGINE["Pulumi engine (inside the pulumi CLI)"]
        RM["Resource monitor<br/>(RegisterResource RPCs)"]
        SE["Step generator + executor<br/>(DAG, diff, parallelism)"]
        SNAP["Snapshot manager<br/>+ journaler"]
        POL["Policy analyzer host"]
    end

    subgraph PROV["Resource provider plugins"]
        PAWS["pulumi-resource-aws"]
        PK8S["pulumi-resource-kubernetes"]
        PNAT["pulumi-resource-azure-native"]
        PTF["pulumi-resource-terraform-provider<br/>(any TF/OpenTofu provider)"]
    end

    subgraph BACKEND["State backend"]
        PCLOUD["Pulumi Cloud<br/>(SaaS or self-hosted)"]
        SELF["DIY backend<br/>(S3, Azure Blob, GCS, PostgreSQL, file)"]
    end

    ESC["Pulumi ESC<br/>(environments, secrets, config)"]

    TS --> LHN
    PY --> LHP
    GO --> LHG
    CS --> LHD
    JA --> LHJ
    YH --> LHY

    LHN -->|gRPC| RM
    LHP -->|gRPC| RM
    LHG -->|gRPC| RM
    LHD -->|gRPC| RM
    LHJ -->|gRPC| RM
    LHY -->|gRPC| RM

    RM --> SE
    SE -->|gRPC| PAWS
    SE -->|gRPC| PK8S
    SE -->|gRPC| PNAT
    SE -->|gRPC| PTF
    SE --> POL
    SE --> SNAP
    SNAP --> PCLOUD
    SNAP --> SELF
    ESC -->|"stack config via environment imports"| RM

Engine

The engine is Go code in the pulumi/pulumi repository (pkg/engine, pkg/resource/deploy) and runs inside the pulumi CLI process (or inside a Pulumi Deployments runner or Automation API host). It:

  1. Starts the language host for the project's runtime: and exposes a resource monitor gRPC endpoint to it.
  2. Receives RegisterResource, Invoke and RegisterResourceOutputs calls as the program runs.
  3. Generates steps (create, update, replace, delete, read, import) by diffing each registration against the prior snapshot, and executes independent steps in parallel while respecting the dependency DAG.
  4. Calls policy packs (analyzers) during preview and update.
  5. Persists the new snapshot, entry by entry, to the backend.
  6. On pulumi refresh, reads the real state of every resource through its provider to detect drift.

Language hosts and providers are separate plugin processes. The engine talks to all of them over gRPC, which is what lets one engine support many languages and many providers without linking them in.

Correction

An earlier version of this page named pulumi-language-go as the engine. pulumi-language-go is the Go language host plugin, not the engine.


Language Host Plugins

Pulumi supports multiple languages through language host plugins. Each host:

  • Executes the user's program in its native runtime.
  • Intercepts resource constructor calls (new aws.s3.Bucket(...), aws.s3.Bucket(...)).
  • Translates those calls into gRPC RegisterResource messages to the engine's resource monitor.

The language host protocol is the gRPC service pulumirpc.LanguageRuntime. Its key methods are GetRequiredPlugins/GetRequiredPackages (declare which providers the program needs), Run (execute the program), InstallDependencies, and GenerateProgram/GenerateProject (used by pulumi convert).

Supported runtimes and their minimum versions are in Reference: Language Runtime Matrix. Recent changes worth knowing:

  • Bun is a first-class Node.js-family runtime (runtime: bun, docs say since 3.227.0). Function serialization and dynamic providers do not work on Bun.
  • Node.js SDK requires Node.js 22+ since 3.249.0 (2026-07-01).
  • Pulumi HCL (runtime: hcl) runs .tf files on the Pulumi engine. The CLI bundles the HCL language host since 3.235.0 (2026-05-05), and the docs require CLI 3.256.0+.
  • Java is officially supported for Java only. Kotlin/Scala/Groovy can consume the Maven artifact but are not officially supported.

Resource Model

Resource Types

Pulumi defines these primary resource abstractions:

Type Purpose Example
CustomResource Maps to a single cloud resource managed by a provider aws.s3.Bucket, azure-native.compute.VirtualMachine
ComponentResource Logical grouping of child resources, no provider CRUD of its own awsx.ec2.Vpc, your own abstractions
ProviderResource An explicitly configured provider instance (region, credentials) new aws.Provider("us-west-2", {...})

Components can be packaged as multi-language components, so a component written in one language can be consumed from the others. This is the basis of the IDP private registry.

Resource URN

Every resource is identified by a URN (Uniform Resource Name):

urn:pulumi:<stack>::<project>::<qualified type>::<name>

Example:

urn:pulumi:production::my-app::aws:s3/bucket:Bucket::my-bucket

The URN encodes the stack, project, resource type (including parent component types) and logical name. It is the stable identity across updates. Renaming a resource or moving it under a new parent changes its URN, which Pulumi treats as delete-and-create unless you declare an aliases option.

Inputs and Outputs

  • Inputs: values passed to a resource constructor. They can be plain values, promises, or Output<T>.
  • Outputs: values not known until the resource is created. Output<T> carries three things: the eventual value, the set of resources it depends on, and whether it is secret.

Output<T> is Pulumi's core mechanism for chaining resource dependencies. Passing one resource's output into another resource's input records the dependency edge automatically:

const bucket = new aws.s3.Bucket("my-bucket");
const obj = new aws.s3.BucketObject("my-object", {
    bucket: bucket.bucket,  // Output<string>: dependency tracked, obj waits for bucket
    source: new pulumi.asset.FileAsset("./dist/app.zip"),
});

The SDK collects every Output<T> referenced during registration. The engine builds a DAG from these references and runs independent resources in parallel. You can add edges that data flow does not show with the dependsOn option.


Provider Plugins

Providers are separate processes that implement the gRPC service pulumirpc.ResourceProvider. A provider is one of three kinds:

  • Native providers are generated directly from a cloud's API specification (for example azure-native, aws-native from the AWS Cloud Control API), so new services appear quickly.
  • Bridged providers wrap a Terraform provider with pulumi-terraform-bridge (for example aws, gcp, azure, cloudflare). Most of the Pulumi Registry is bridged.
  • Parameterized providers take a parameter at install time and generate a local SDK. The Any Terraform Provider package is the main example.

Provider Lifecycle RPCs

This sequence shows the RPCs the engine sends to one provider during an update.

sequenceDiagram
    participant Engine as Pulumi engine
    participant Provider as pulumi-resource-aws

    Engine->>Provider: GetSchema
    Provider-->>Engine: Package schema (JSON)
    Engine->>Provider: CheckConfig + Configure (region, credentials)
    Provider-->>Engine: ConfigureResponse

    loop For each registered resource
        Engine->>Provider: Check (validate and default inputs)
        Provider-->>Engine: CheckResponse
        Engine->>Provider: Diff (old state vs new inputs)
        Provider-->>Engine: DiffResponse (replaces, stables)
        alt Create
            Engine->>Provider: Create
            Provider-->>Engine: ID + outputs
        else Update
            Engine->>Provider: Update
            Provider-->>Engine: New outputs
        else Delete
            Engine->>Provider: Delete
            Provider-->>Engine: Empty response
        end
    end

Terraform Bridge

The Terraform bridge (pulumi-terraform-bridge) adapts a Terraform provider (built on the Terraform Plugin SDK or the Plugin Framework) into a Pulumi provider. It does three things:

  1. Schema translation: it reads the Terraform provider schema and produces a Pulumi package schema, from which Pulumi generates typed SDKs for each language.
  2. Resource lifecycle mapping: Terraform's plan/apply CRUD functions are exposed through Pulumi's Check/Diff/Create/Read/Update/Delete RPCs.
  3. State mapping: Terraform's flat attribute state is converted to Pulumi's structured resource outputs.

There are two ways to use a Terraform provider with Pulumi:

Path How When
Pre-bridged Registry package npm install @pulumi/cloudflare, pip install pulumi-cloudflare, ... A maintained Pulumi package exists
Any Terraform Provider pulumi package add terraform-provider <ns>/<name> [version] generates a local typed SDK No Pulumi package exists, an internal provider, or a version the Registry does not publish

The Any Terraform Provider resolves from the OpenTofu registry by default. That registry mirrors the Terraform registry, so a provider published to either one is available. You can also point it at a provider binary on disk. This makes the Terraform and OpenTofu provider ecosystems (thousands of providers) usable from Pulumi. The opentofu/registry metadata repo lists 4,603 providers (counted 2026-09-27).

Pulumi HCL

Pulumi HCL (runtime: hcl, repo pulumi/pulumi-hcl) goes a step further. It runs ordinary Terraform/OpenTofu .tf files, including variable, output and required_providers blocks and *.tfvars files, on the Pulumi engine. Providers resolve like OpenTofu (bridged automatically), and a pulumi/ source prefix selects a native Pulumi provider. Teams can keep HCL syntax and still get Pulumi state, secrets, ESC and policies. It is a language plugin, not a Terraform binary, so behaviour can differ from Terraform in edge cases.


Resource Lifecycle Diagram

This flowchart shows how the engine decides what to do with each registered resource during pulumi up.

flowchart TD
    A["pulumi up"] --> B["Language host runs the program"]
    B --> C["SDK sends RegisterResource"]
    C --> D["Engine adds node to resource DAG"]
    D --> E{"URN in prior snapshot?"}
    E -->|No| F["Create: provider Create"]
    E -->|Yes| G["Diff: provider Diff"]
    G --> H{"Changes detected?"}
    H -->|No change| I["Same step, keep existing"]
    H -->|In-place update| J["Update: provider Update"]
    H -->|Requires replacement| K["Create replacement,<br/>then delete old<br/>(or delete first if deleteBeforeReplace)"]
    F --> L["Journal entry written to backend"]
    J --> L
    K --> L
    I --> L
    L --> M{"Program finished?"}
    M -->|No| C
    M -->|Yes| N["Delete resources no longer registered,<br/>persist final snapshot"]

Deployment Engine

This sequence shows one resource flowing from user code to the cloud and back.

sequenceDiagram
    participant User as User code
    participant SDK as Pulumi language SDK
    participant Engine as Pulumi engine
    participant Provider as Provider plugin
    participant Cloud as Cloud API

    User->>SDK: new aws.s3.Bucket(...)
    SDK->>Engine: RegisterResource (gRPC)
    Engine->>Engine: Diff against snapshot, schedule step
    Engine->>Provider: Create / Update / Delete
    Provider->>Cloud: Cloud API calls
    Cloud-->>Provider: Resource attributes
    Provider-->>Engine: ID + outputs
    Engine-->>SDK: Resolve Outputs
    SDK-->>User: Dependent resources proceed, stack outputs exported

State Management

State Backend Options

Pulumi records state in a backend. There are two families:

  • Pulumi Cloud (https://api.pulumi.com, or a self-hosted Pulumi Cloud on the Enterprise edition): state is written through a transactional API. The service provides locking, update history, RBAC, audit logs, and the other Pulumi Cloud services.
  • DIY backends: object storage you manage (AWS S3 and S3-compatible stores such as MinIO or Ceph, Azure Blob Storage, Google Cloud Storage), a PostgreSQL database, or the local filesystem. All DIY backends use a basic file-based lock (.pulumi/locks/) by default and keep checkpoint history in .pulumi/history/.

Correction

Earlier versions of this page said the S3 backend locks with DynamoDB, Azure Blob locks with a blob lease, and the local backend has no locking. Pulumi does not use DynamoDB. All DIY backends use lock files written to the same store. See Reference: State Backends.

DIY non-project mode is being removed

The legacy DIY layout where stacks are not scoped by project was deprecated in 3.228.0 (2026-03-25). Since 3.257.0 (2026-08-13) it is an error unless PULUMI_DIY_BACKEND_IGNORE_DEPRECATION_ERROR is set. Migrate with pulumi state upgrade (see How-to Guides). Pulumi's stated removal window is "before the end of 2026" (per the v3.257.0 release notes).

State Format and Journaling

  • State is a JSON checkpoint (deployment) with every resource's URN, type, provider reference, inputs, outputs, dependencies and parent.
  • Secret values are encrypted individually inside the checkpoint rather than encrypting the whole file. Each stack has a secrets provider: the Pulumi Cloud service key, a passphrase, or a cloud KMS (awskms://, azurekeyvault://, gcpkms://, hashivault://).
  • Since 3.225.0 (2026-03-04) the engine sends journal entries (one per step) to backends that support them instead of rewriting the whole snapshot after each step. Pulumi Cloud replays the journal into snapshots, which improves performance on large stacks and makes recovery from interrupted updates easier. You can turn it off with PULUMI_DISABLE_JOURNALING. DIY backends still write checkpoints to blob storage, and blob storage is not transactional. That is why DIY backends cannot recover transparently from every partial failure.
  • Pulumi Cloud rejects a second concurrent update on the same stack (HTTP 409 "another update is currently in progress"). DIY backends reject it through the lock file. pulumi cancel (Pulumi Cloud) or removing a stale lock releases a stuck stack.

Stack References

A stack reference lets one stack read another stack's outputs:

const other = new pulumi.StackReference("org/network/prod");
const vpcId = other.getOutput("vpcId");

This is how Pulumi composes layered infrastructure (network, then cluster, then apps) without one giant stack. With ESC the same outputs can also be consumed through the pulumi-stacks provider in an environment.


Automation API

The Automation API embeds the engine in application code. It drives the same preview/up/refresh/destroy operations programmatically, without scripting the CLI by hand. It still needs the pulumi CLI binary installed, because it invokes the CLI under the hood:

import * as auto from "@pulumi/pulumi/automation";

const stack = await auto.LocalWorkspace.createOrSelectStack({
    stackName: "dev",
    projectName: "my-app",
    program: async () => { /* declare resources inline */ },
});

const upResult = await stack.up({ onOutput: console.info });
console.log(`Update result: ${upResult.summary.result}`);

Typical uses: custom CLIs and developer portals, multi-stack orchestrators, SaaS tenant provisioning, and integration tests. The Automation API ships inside each language SDK (@pulumi/pulumi/automation, pulumi.automation, github.com/pulumi/pulumi/sdk/v3/go/auto, Pulumi.Automation).

Import path

An earlier version of this page imported from @pulumi/automation. The Node.js Automation API is part of the core package and is imported from @pulumi/pulumi/automation.


Pulumi Cloud Services

Pulumi Cloud is more than a state store. The product areas below all build on the same organization, RBAC and audit model.

Pulumi ESC (Environments, Secrets, and Configuration)

ESC is a hierarchical key-value document store for secrets and configuration:

  • Environments are YAML documents that can imports: other environments (org, team, project layering) and are versioned with tags and revisions.
  • Providers (fn::open::<provider>) resolve values dynamically when an environment is opened.
  • Login providers exchange a Pulumi-issued OIDC token for short-lived credentials: aws-login, azure-login, gcp-login, and others.
  • Secrets providers pull from AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault, 1Password, and others.
  • Consumers: Pulumi stacks (environment: in Pulumi.<stack>.yaml), pulumi env run -- <cmd>, the ESC SDKs (Node.js, Python, Go, .NET), the REST API, and Kubernetes through the External Secrets Operator.
  • Tooling changes in 2026: the ESC engine and CLI were folded into the pulumi/pulumi monorepo (3.251.0), and the standalone esc CLI is retired in favour of pulumi env (esc v0.26.0 prints a retirement notice). pulumi env setup aws|azure|gcp (3.261.0) creates the cloud OIDC trust and the environments in one step.
  • ESC requires Pulumi Cloud. Using it with a DIY state backend means logging in to Pulumi Cloud for ESC as well.

This sequence shows the OIDC flow for dynamic AWS credentials:

sequenceDiagram
    participant CLI as pulumi env run / pulumi up
    participant ESC as Pulumi Cloud ESC
    participant STS as AWS STS
    participant Target as Command or Pulumi program

    CLI->>ESC: Open environment (e.g. aws/prod)
    ESC->>ESC: Evaluate imports and fn::open::aws-login
    ESC->>ESC: Mint OIDC token (issuer api.pulumi.com/oidc)
    ESC->>STS: AssumeRoleWithWebIdentity (role ARN, token)
    STS-->>ESC: Temporary credentials
    ESC-->>CLI: Resolved values, secrets marked secret
    CLI->>Target: Inject as env vars or stack config

Pulumi Neo (AI Agent)

Neo is Pulumi's infrastructure agent. It was announced in public preview in September 2025 and is powered by Anthropic Claude models through Amazon Bedrock. Enterprise can bring its own Anthropic key (BYOK). Neo reads the organization's live state in Pulumi Cloud and can:

  • answer questions about infrastructure
  • investigate failed updates (pulumi neo --debug-update)
  • run pulumi preview
  • open pull requests against IaC code
  • review PRs
  • run scheduled automations

It is reachable from the Pulumi Cloud console, the CLI (pulumi neo, visible by default since 3.241.0, 2026-05-18), editors via the Agent Client Protocol (pulumi neo acp, 3.254.0), Slack (@Neo), GitHub PRs (@pulumi-neo), and other agents via the Pulumi MCP server. Plan Mode, task modes, and read-only mode control autonomy, and Neo never has more access than the invoking user.

Neo replaced the earlier AI features. Pulumi Copilot (2024) is no longer a separate product in the docs. The pulumi ai web command was removed in 3.246.0, and the "Pulumi AI" mode of pulumi new was retired in 3.256.0 (2026-08-04) because its backing service was shut down.

Internal Developer Platform (IDP)

Pulumi IDP (launched 2025) lets platform teams publish building blocks and developers consume them:

  • Private registry: components and templates published by the organization.
  • Organization templates: scaffolds for new projects.
  • No-code stacks and the New Project Wizard: deploy from the console without writing code.
  • Services: groupings of stacks, environments and resources.
  • Integrations: a Backstage plugin and a "Deploy with Pulumi" button.

It is a layer over Pulumi IaC, ESC and Deployments rather than a separate engine. In the current editions IDP is part of Pro and above.

Discovery and Governance (formerly Insights)

What launched as Pulumi Insights (and Insights 2.0 with Infrastructure Account Scanning) is now documented as Discovery & governance (/docs/insights/ redirects to /docs/discovery-governance/). It covers:

  • Discovery: scans cloud accounts and indexes all resources, including those created by Terraform, CloudFormation or by hand. It provides resource search (structured or natural language) and Visual Import into Pulumi.
  • Policies: policy packs and policy groups applied in preventative mode (block deployments) or audit mode (evaluate discovered resources). Pre-built compliance packs cover CIS, HITRUST, NIST, PCI DSS, ISO 27001 and CMMC, and violations are tracked as findings with remediation.
  • Context API: queries dependencies, ownership, stack consumers and change impact.

The CLI side is pulumi insights account ... (account and scan management, 3.241.0+) and pulumi policy ....

Pulumi Deployments and the Kubernetes Operator

  • Pulumi Deployments runs pulumi operations on Pulumi-hosted or customer-managed runners. It is triggered by git push, the REST API, schedules, drift detection or TTL stacks. Deployment settings live only in Pulumi Cloud since 3.251.0 (the Pulumi.<stack>.deploy.yaml file was removed).
  • The Pulumi Kubernetes Operator (v2.x, latest 2.9.1 on 2026-09-03) reconciles Stack custom resources inside a cluster. Since v2 each stack runs in its own workspace pod rather than in the operator process.

Security Model

Authentication Model

Principals authenticate to Pulumi Cloud in one of these ways. The table lists the method and where it is used.

Method Use case
Personal access token Individual CLI use (pulumi login)
Organization / team access tokens CI/CD and automation not tied to a person
OIDC token exchange CI systems (GitHub Actions, GitLab, and others) exchange their OIDC token for a short-lived Pulumi token, so no long-lived Pulumi token is stored
SAML SSO + SCIM Enterprise identity federation and user/group sync (SAML on Pro+, SCIM on Enterprise in the current editions)
GitHub / GitLab / Atlassian identity Developer login

This diagram shows how identities flow from people and pipelines through Pulumi Cloud to cloud providers.

graph TB
    subgraph Callers
        Dev["Developer<br/>(pulumi CLI, IDE, pulumi neo)"]
        CI["CI/CD pipeline<br/>(GitHub Actions OIDC)"]
    end
    subgraph PulumiCloud["Pulumi Cloud"]
        AuthN["Identity<br/>(SAML SSO, OIDC exchange, tokens)"]
        RBAC["RBAC<br/>(roles, permission sets, teams)"]
        StateStore["Stack state<br/>(per-stack secret keys)"]
        ESCN["ESC environments<br/>(fn::open login providers)"]
        Audit["Audit log"]
    end
    subgraph Clouds["Cloud providers"]
        AWS["AWS STS"]
        Azure["Entra ID workload identity"]
        GCP["GCP workload identity federation"]
    end
    Dev --> AuthN
    CI --> AuthN
    AuthN --> RBAC
    RBAC --> StateStore
    RBAC --> ESCN
    RBAC --> Audit
    ESCN -->|"OIDC token exchange"| AWS
    ESCN -->|"OIDC token exchange"| Azure
    ESCN -->|"OIDC token exchange"| GCP

Secrets Management

Pulumi encrypts individual secret values inside stack state:

  • Pulumi Cloud secrets provider (default on Pulumi Cloud): encryption and decryption happen in the service with a per-stack key, so the client never holds the key. Customer-managed keys are available on Pro and above.
  • Passphrase: the key is derived from PULUMI_CONFIG_PASSPHRASE. This is common with DIY backends.
  • Cloud KMS / Vault: AWS KMS, Azure Key Vault, Google Cloud KMS, or HashiCorp Vault Transit, selected with --secrets-provider or pulumi stack change-secrets-provider.

Secretness is tracked transitively. If a secret Output feeds a computation, the result is also secret and is masked in CLI output and in the Pulumi Cloud console. --show-secrets reveals values in diffs (3.257.0+).

Authorization and RBAC

Pulumi Cloud RBAC is built from scopes (for example stack:read, environment:open) bundled into permission sets (for example Stack Read / Write / Admin). Permission sets are applied to entities (stacks, environments, cloud accounts) inside roles, which are assigned to users, teams or tokens. The built-in roles are Admin, Member and Billing Manager. Custom roles and advanced RBAC are part of the Pro edition and above. Stack creators automatically get a Stack Admin grant on the stacks they create. See Reference: Pulumi Cloud RBAC.

Correction

An earlier table on this page listed roles such as "Team Admin", "Contributor", "Reader" and "Deploy Admin". Those are not the Pulumi-defined role names in the current RBAC docs. The model is roles plus permission sets.

Policy as Code (CrossGuard)

Policy packs (historically branded CrossGuard) are programs that validate resources during preview and up (resource and stack validation) and, with Discovery, against discovered resources in audit mode. Packs can be written in TypeScript/JavaScript, Python, and (via policyx) Go. They run as analyzer plugins next to the engine. Enforcement levels are advisory (warn), mandatory (block), remediate (fix the resource inputs before deployment) and disabled. Organization-managed enforcement through policy groups requires Pro or above. Running a pack locally with pulumi preview --policy-pack <dir> works on every edition. A recipe is in How-to Guides.

Provider Credential Security

Pulumi prefers short-lived credentials from ESC login providers or CI OIDC over static keys stored in config:

Provider Mechanism
AWS sts:AssumeRoleWithWebIdentity with a Pulumi Cloud OIDC token (fn::open::aws-login)
Azure Workload identity federation (fn::open::azure-login)
GCP Workload identity federation (fn::open::gcp-login)

Stack References Security

Stack outputs are readable by anyone who can read the stack

  • Anyone with read access to a referenced stack can read all of its outputs.
  • Use RBAC (permission sets on the stack) to limit which teams can read sensitive stacks.
  • Secret outputs stay encrypted and secret when read through a stack reference.
  • Use separate organizations for hard isolation between environments or tenants.

State File Security

  • Pulumi Cloud: state is encrypted in transit and at rest. Secrets inside state are additionally encrypted per stack. Every update is recorded in stack history and the audit log. Pulumi documents SOC 2 Type 2 compliance. The Pulumi Platform Security Whitepaper (last updated July 2026, checked 2026-09-27) describes envelope encryption: content is encrypted with AES-GCM using 256-bit data keys, and the data keys are wrapped by key-encryption keys held in an external KMS or HSM. Customers can supply their own KMS keys. The whitepaper does not name a minimum TLS version.
  • DIY backends: the state file is only as safe as the bucket or database. Encryption at rest, versioning, access policies and access logging are your responsibility. The checklist is in Reference: Hardening Checklist.

Comparison with Terraform / OpenTofu

Aspect Pulumi Terraform / OpenTofu
Language General-purpose (TS/JS, Python, Go, .NET, Java) plus YAML and HCL HCL (declarative DSL)
Provider ecosystem Native + bridged Registry packages + any TF/OpenTofu provider via terraform-provider Native registry providers
State secrets Per-value encryption in state Terraform: plaintext state (protect the backend). OpenTofu: optional whole-state encryption (1.7+)
Testing Language test frameworks with mocks, plus integration tests terraform test / tofu test (1.6+), plan assertions
Embedding Automation API in every SDK No official embedding SDK (wrappers such as terraform-exec drive the CLI)
Component reuse Language packages and multi-language components Modules (HCL)
License Apache 2.0 (CLI/SDKs), Pulumi Cloud proprietary Terraform BUSL 1.1, OpenTofu MPL 2.0

The full three-way comparison is in IaC Comparison.


Design Trade-offs

  • Imperative host, declarative engine. Programs look imperative, but the result is a declarative desired-state graph. The trade-off is that arbitrary code runs during preview. Side effects in the program (API calls, file writes) run on every preview, and values not known until apply are only available inside apply.
  • Unknowns during preview. Outputs of resources that do not exist yet are unknown during preview, so code that branches on them cannot be fully previewed. HCL tools have the same "known after apply" limitation, but in Pulumi it shows up as Output plumbing.
  • Bridged providers track Terraform. Most major Pulumi providers are bridged, so their coverage and bugs follow the upstream Terraform provider. Native providers (azure-native, aws-native) trade that dependence for coverage generated from cloud API specs.
  • SaaS gravity. The engine and SDKs are Apache 2.0, and DIY backends are fully supported. However, ESC, Neo, Deployments, IDP, Discovery and org-level policy enforcement require Pulumi Cloud (SaaS or self-hosted Enterprise).
  • Language runtime cost. Each run starts a language runtime plus provider plugins. Large Node.js or Python programs and very large stacks are dominated by provider and cloud API time, but runtime start-up and plugin download are noticeable in CI. Plugin caching is covered in How-to Guides.

Sources