Skip to main content
If you’re already running Pulumi, migrating to ubx doesn’t mean rewriting your infrastructure — it means lifting the declaration layer to a higher abstraction. Your Pulumi state stays intact. Your existing resources don’t get recreated. ubx convert --from pulumi reads your TypeScript (or Python or Go) Pulumi program and generates equivalent .iac source. The converter handles config lookups (config.get("env")input "env"), resource declarations (new aws.s3.BucketV2("assets", {...})unit "aws_s3_bucket_v2" "assets" {...}), and exports (export const bucket = ...output "bucket" {...}). Cases that require manual attention — complex TypeScript expressions that don’t have .iac equivalents, multi-resource patterns that collapse to sub-blocks — are documented in the MIGRATION.md the converter produces.

What you’ll learn

  • How ubx convert --from pulumi converts Pulumi TypeScript to .iac
  • The common manual fixups required after conversion
  • Why existing Pulumi state is preserved — no reimport needed

Why this matters

Migrating from Pulumi to ubx preserves your Pulumi state entirely — the same S3/GCS/Azure Blob backend, the same resource URNs. No import blocks needed, no resources recreated. ubx is a compiler that targets Pulumi, so the generated program is a valid Pulumi program that picks up where your existing program left off.

The source code

// Post-migration .iac — result of ubx convert + manual fixups.
// See pulumi-before/index.ts for the original Pulumi TypeScript source.

unit "aws_s3_bucket_v2" "assets" {
  bucket     = input.bucket_name
  tags       = { Environment = input.env, ManagedBy = "ubx" }
  versioning = { enabled = true }   // BucketVersioningV2 resource collapsed to sub-block
}

unit "aws_rds_instance" "db" {
  engine                  = "postgres"
  instance_class          = "db.t3.micro"
  identifier              = "${input.env}-db"
  username                = "admin"
  password                = "changeme"
  storage_encrypted       = true
  backup_retention_period = 7
}

How it works

1

Run ubx convert to generate .iac files

ubx convert --from pulumi ./pulumi-before/index.ts --out ./ reads the TypeScript Pulumi program and generates .iac equivalents. The converter handles config.get("x")input "x", new aws.Resource("name", {...})unit "aws_resource" "name" {...}, and export const x = ...output "x" {}.
2

Apply manual fixups from MIGRATION.md

The converter documents cases requiring manual attention. Common fixups: BucketVersioningV2 resource → versioning sub-block inside aws_s3_bucket_v2, complex TypeScript template literals → .iac string interpolation, config.require()input with no default, pulumi.secret() wrapper → ephemeral = true on the input.
3

No import needed — Pulumi state is reused

Unlike Terraform migration, no import blocks are needed. ubx generates a Pulumi TypeScript program that uses the same resource URNs as the original. The first ubx apply runs against the existing Pulumi state and produces no diff if the converted source matches the original program’s configuration exactly.

Common conversion patterns

Pulumi TypeScriptubx .iacNotes
config.get("env") || "dev"input "env" { default = "dev" }Default maps directly
config.require("key")input "key" {} (no default)Required input
new aws.s3.BucketV2("name", {...})unit "aws_s3_bucket_v2" "name" {...}SDK class → unit type
export const x = res.attroutput "x" { value = unit.res.attr }export → output block
BucketVersioningV2 resourceversioning {} sub-blockSub-resource → sub-block
pulumi.secret(value)input "x" { ephemeral = true }Secret wrapping → ephemeral
`${env}-name`"${input.env}-name"Template literal → interpolation

Common mistakes

Complex TypeScript expressions — array .map(), .filter(), conditional object spreading — have no direct .iac equivalent. The converter flags these in MIGRATION.md as requiring manual rewrite. The .iac equivalents are usually for_each blocks, when conditions, or local blocks. Review MIGRATION.md before assuming the conversion is complete.
After conversion, run ubx plan against your existing Pulumi backend. A clean plan (0 creates, 0 modifies, 0 destroys) means the converted .iac source is equivalent to the original Pulumi program. Any diff indicates a conversion gap that needs to be addressed before cutting over.

Run it

# Convert
ubx convert --from pulumi ./pulumi-before/index.ts --out ./

# Review MIGRATION.md and apply manual fixups

# Validate
ubx validate

# Plan against existing Pulumi state — should show 0 changes if conversion is clean
ubx plan    # requires AWS credentials and existing Pulumi state backend

What you learned

ubx convert --from pulumi converts Pulumi TypeScript programs to .iac automatically
Pulumi state is reused — no import blocks needed, no resources recreated
A clean ubx plan (0 diffs) confirms the converted source is equivalent to the original program

Next steps

Migrate from Terraform

Convert Terraform .tf files to .iac

Multi-runtime compilation

Change the runtime target after migration