> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ubiquex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Pulumi

> Convert existing Pulumi TypeScript programs to .iac with ubx convert.

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

<Info>
  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.
</Info>

***

## The source code

<CodeGroup>
  ```hcl main.iac theme={"theme":"css-variables"}
  // 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
  }
  ```

  ```typescript pulumi-before/index.ts theme={"theme":"css-variables"}
  // Original Pulumi TypeScript program — before migration
  import * as pulumi from "@pulumi/pulumi";
  import * as aws from "@pulumi/aws";

  const config = new pulumi.Config();
  const env = config.get("env") || "prod";
  const bucketName = config.require("bucketName");

  const assetsBucket = new aws.s3.BucketV2("assets", {
    bucket: bucketName,
    tags: { Environment: env, ManagedBy: "pulumi" },
  });

  // Separate versioning resource — collapses to sub-block in ubx
  const versioning = new aws.s3.BucketVersioningV2("assets-versioning", {
    bucket: assetsBucket.id,
    versioningConfiguration: { status: "Enabled" },
  });

  const db = new aws.rds.Instance("db", {
    engine: "postgres",
    instanceClass: "db.t3.micro",
    identifier: `${env}-db`,
    username: "admin",
    password: "changeme",
    storageEncrypted: true,
    skipFinalSnapshot: true,
  });

  export const bucket = assetsBucket.bucket;
  export const dbEndpoint = db.endpoint;
  ```

  ```hcl inputs.iac theme={"theme":"css-variables"}
  input "env" {
    type    = "string"
    default = "prod"
  }

  input "bucket_name" {
    type    = "string"
    default = "myapp-assets"
  }
  ```

  ```hcl outputs.iac theme={"theme":"css-variables"}
  output "bucket"      { value = unit.aws_s3_bucket_v2.assets.bucket }
  output "db_endpoint" { value = unit.aws_rds_instance.db.endpoint }
  ```

  ```hcl ubx.iac theme={"theme":"css-variables"}
  ubx {
    name    = "migration-from-pulumi"
    runtime = "typescript"
    stacks  = ["dev", "staging", "prod"]
  }

  backend "s3" {
    bucket = "my-ubx-state"
    region = "us-east-1"
    key    = "${stack}/migration-from-pulumi.tfstate"
  }
  ```
</CodeGroup>

***

## How it works

<Steps>
  <Step title="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" {}`.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

***

## Common conversion patterns

| Pulumi TypeScript                    | ubx .iac                               | Notes                            |
| ------------------------------------ | -------------------------------------- | -------------------------------- |
| `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.attr`          | `output "x" { value = unit.res.attr }` | export → output block            |
| `BucketVersioningV2` resource        | `versioning {}` sub-block              | Sub-resource → sub-block         |
| `pulumi.secret(value)`               | `input "x" { ephemeral = true }`       | Secret wrapping → ephemeral      |
| `` `${env}-name` ``                  | `"${input.env}-name"`                  | Template literal → interpolation |

***

## Common mistakes

<Warning>
  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.
</Warning>

<Tip>
  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.
</Tip>

***

## Run it

```bash theme={"theme":"css-variables"}
# 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

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

***

## Next steps

<CardGroup cols={2}>
  <Card title="Migrate from Terraform" href="/v1/tutorials/54-migration-from-terraform">
    Convert Terraform .tf files to .iac
  </Card>

  <Card title="Multi-runtime compilation" href="/v1/tutorials/42-runtime-typescript">
    Change the runtime target after migration
  </Card>
</CardGroup>

***

<Info>
  Full runnable example: [github.com/ubiquex/ubx-examples/55-migration-from-pulumi](https://github.com/ubiquex/ubx-examples/tree/main/55-migration-from-pulumi)
</Info>
