You can import existing infrastructure into Pulumi without needing to rewrite your entire setup.

Let’s see it in action. Imagine you have an AWS S3 bucket that was created manually, not through Pulumi.

import pulumi
import pulumi_aws as aws

# This is the ID of the existing S3 bucket we want to import.
# Replace this with your actual bucket name.
existing_bucket_id = "my-manually-created-bucket"

# We're declaring a new S3 bucket resource, but we'll tell Pulumi
# to "adopt" the existing one using its ID.
s3_bucket = aws.s3.Bucket(
    "my-bucket-resource",
    # The 'id' argument is crucial for importing.
    # It tells Pulumi which existing resource this Pulumi resource
    # should represent.
    id=existing_bucket_id,
    # Other properties can be set here if you want Pulumi to manage them
    # going forward. If you leave them out, Pulumi will read the current
    # state and assume those properties match.
    acl="private",
    tags={
        "environment": "production",
        "managed_by": "pulumi",
    }
)

# Export the name of the bucket
pulumi.export("bucket_name", s3_bucket.id)

When you run pulumi up with this code, Pulumi detects that my-bucket-resource has an id specified. It then queries the AWS provider for a resource with that ID and associates it with your my-bucket-resource Pulumi resource. If the resource exists, Pulumi will record its current state and manage it from this point forward. If it doesn’t exist, pulumi up will fail, indicating that the resource with the specified ID could not be found.

The core problem Pulumi Import solves is bridging the gap between infrastructure provisioned outside of Pulumi (manually, via another tool, or an older Pulumi stack) and the desire to manage it consistently with Pulumi. It allows for gradual migration and avoids the "big bang" rewrite of existing, stable infrastructure. Pulumi Import effectively performs a "read" operation on the cloud provider’s API to get the current state of the existing resource and then registers that state within your Pulumi deployment.

Internally, Pulumi uses the resource’s unique identifier (like an ARN for an AWS resource, or a name for simpler resources) to locate it in the cloud provider. Once found, Pulumi fetches all of its current properties. This fetched state is then merged with your Pulumi program. If your program specifies properties that differ from the existing resource’s state, Pulumi will attempt to update the resource to match your program’s desired state during the pulumi up operation. If a property is not specified in your Pulumi program but exists on the resource, Pulumi will simply record its current value and manage it.

The exact levers you control are the Pulumi resource declaration itself and the id argument. The id argument is the key that links your Pulumi resource definition to the pre-existing cloud resource. Any other arguments in your Pulumi resource definition represent the desired state. Pulumi will compare the desired state with the actual state it reads from the cloud provider and act accordingly. If you want Pulumi to enforce specific configurations (like tags, access control lists, etc.) on an imported resource, you must include those properties in your Pulumi program.

Many people think of pulumi import as a command-line operation. While pulumi import is a powerful command that can generate the Pulumi code for you, the underlying mechanism is the id argument within your Pulumi program. You can manually construct the Pulumi code to import resources by simply adding the id argument to your resource definition, and then running pulumi up. This approach gives you more control over how the resource is represented in your code from the outset.

The next hurdle you’ll likely encounter is understanding how Pulumi handles drift detection and updates for imported resources.

Want structured learning?

Take the full Pulumi course →