Skip to main content
Not every resource starts its life in ubx. A bucket created manually in the AWS console. An RDS instance provisioned before the team adopted IaC. A VPC that predates the current stack by years. These resources exist, they’re in use, and recreating them from scratch is either impossible (stateful databases) or dangerous (anything currently serving traffic). The import block is how you bring them under management: you write a unit block describing the resource’s desired configuration, add a matching import block with the resource’s cloud provider ID, and on the next ubx apply, Pulumi reads the live resource’s state, writes it into Pulumi’s state file, and begins managing it going forward. No recreation. No downtime. No data loss.

What you’ll learn

  • How import brings an existing cloud resource under ubx management
  • How the id value maps to the cloud provider’s resource identifier
  • What happens at validate time vs apply time

Why this matters

ubx validate only checks the import block’s syntax — it does not contact AWS to verify the resource exists. The actual import (writing the resource into Pulumi state) happens on the first ubx apply, which requires AWS credentials and the resource to already exist in your account.

The source code

# unit block describes the desired final state after import.
# The attributes should match what the existing resource actually has.
unit "aws_s3_bucket_v2" "assets" {
  bucket = "my-existing-bucket"
}

How it works

1

ubx validate checks syntax only

ubx validate confirms the import block’s type and name match an existing unit block, and that the id field is present. It does not contact AWS or verify the resource exists. Validation passes even if the bucket doesn’t exist yet.
2

ubx apply reads the live resource state

On the first apply after an import block is added, Pulumi calls the AWS API to read the resource’s current configuration and writes it into the Pulumi state file. The resource is now tracked in state as if it had been created by Pulumi originally.
3

Subsequent applies manage the resource normally

After the first import apply, the import block can be removed — the resource is now in Pulumi state. Future ubx apply runs treat it like any other managed resource: diffs are computed, updates are applied if attributes change.

What ubx generates

// Generated by ubx — do not edit
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// The import option tells Pulumi to adopt the existing resource
const assets = new aws.s3.BucketV2("assets", {
    bucket: "my-existing-bucket",
}, {
    import: "my-existing-bucket",  // generated from the import block
});

export const bucketName = assets.bucket;

Common mistakes

If the attributes in your unit block don’t match the live resource’s actual configuration, the first import apply will produce a diff and immediately try to update the resource to match your .iac declaration. Before importing, inspect the live resource’s attributes and ensure your unit block reflects them accurately to avoid unintended updates on first apply.
After the first ubx apply succeeds and the resource is in Pulumi state, remove the import block. Leaving it in place causes Pulumi to attempt re-import on every subsequent apply, which is a no-op but adds noise to the plan output.

Run it

ubx validate              # syntax check only — does not contact AWS
ubx plan                  # requires AWS credentials; shows the import in the plan
ubx apply                 # performs the import — resource must exist in AWS
# After successful apply: remove the import block

What you learned

import brings an existing cloud resource under ubx management without recreating it
ubx validate only checks syntax — the actual import requires AWS credentials and the resource to exist
Remove the import block after the first successful apply — the resource is now in Pulumi state

Next steps

moved block

Rename a resource in state without destroying it

import block reference

Full import block syntax and provider ID formats