Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Infrastructure as Code By James Joyner IV · · 9 min read

IaC Error Guide: 'resource already exists' Pulumi Update Failed

Fix Pulumi's 'update failed / resource already exists' error: reconcile drifted state, import out-of-band resources, clear partial updates, and remove protect.

  • #iac
  • #troubleshooting
  • #errors
  • #pulumi

Overview

This error happens when Pulumi tries to create a resource during pulumi up, but the underlying cloud provider reports that an object with the same name or identifier is already present. Pulumi’s state file has no record of the resource, so it plans a create; the cloud API then rejects the create because the real object exists. The update aborts and the stack is left partially applied.

You will see this at the end of a pulumi up:

Diagnostics:
  aws:s3:Bucket (assets):
    error: creating S3 Bucket (my-bucket-name): operation error S3: CreateBucket,
    https response error StatusCode: 409, BucketAlreadyOwnedByYou: Your previous
    request to create the named bucket succeeded and you already own it.

  pulumi:pulumi:Stack (infra-prod):
    error: update failed

It occurs whenever the desired state in code diverges from what Pulumi has tracked in state: a resource created outside Pulumi, a previous update that failed after the cloud object was created but before state was written, or a duplicate logical name. The fix is almost always to reconcile state with reality rather than to keep retrying pulumi up.

Symptoms

  • pulumi up ends with error: update failed and a provider 409/AlreadyExists diagnostic.
  • The resource exists in the cloud console but not in pulumi stack export.
  • A prior pulumi up was interrupted (Ctrl-C, CI timeout) and now reports a pending operation.
  • pulumi destroy cannot remove a resource because of a protect flag.
pulumi up --yes
Updating (prod):
     Type                 Name           Status                  Info
     pulumi:pulumi:Stack  infra-prod     **failed**              1 error
 +   └─ aws:s3:Bucket     assets         **creating failed**     1 error

Diagnostics:
  aws:s3:Bucket (assets):
    error: creating S3 Bucket (my-bucket-name): ... BucketAlreadyOwnedByYou

Common Root Causes

1. The resource was created out-of-band and is not in state

Someone created the object manually (console, CLI, another tool), so Pulumi has no URN for it and tries to create it again.

pulumi stack export | jq '.deployment.resources[].urn' | grep -i bucket
aws s3api head-bucket --bucket my-bucket-name
(no matching URN in state)
(head-bucket returns 0 — the bucket exists in AWS)

The bucket exists in AWS but not in Pulumi state, so the create collides.

2. A previous update failed after creating the cloud object

The provider created the resource, but Pulumi crashed or was cancelled before recording it. State is missing the resource even though the API call succeeded.

pulumi stack export | jq '.deployment.pending_operations'
[
  {
    "type": "creating",
    "resource": {
      "urn": "urn:pulumi:prod::infra::aws:s3/bucket:Bucket::assets"
    }
  }
]

A non-empty pending_operations means the last update was interrupted mid-flight.

3. Duplicate logical (resource) name in the program

Two resources declared with the same logical name and the same physical name resolve to the same cloud object, so the second create collides.

grep -rn 'new aws.s3.Bucket' ./
./index.ts:14:const assets = new aws.s3.Bucket("assets", { bucket: "my-bucket-name" });
./storage.ts:9:const cdn = new aws.s3.Bucket("cdn", { bucket: "my-bucket-name" });

Two different logical names but the same hard-coded bucket value both want my-bucket-name.

4. Stale state vs. the real cloud (drift)

State claims the resource was deleted (or never existed), but it is still live in the provider. A pulumi refresh reveals the gap.

pulumi refresh --diff --yes
Refreshing (prod):
 ~  aws:s3:Bucket  assets  refreshing
 -  aws:s3:Bucket  assets  deleted
Resources:
    - 1 deleted

Refresh marks the resource deleted in state, but the bucket still exists in AWS — the next up will try to recreate it and collide.

5. The resource is marked protect

A protect: true resource cannot be replaced or deleted, so any plan that needs to recreate it blocks the update.

pulumi stack export | jq '.deployment.resources[] | select(.protect==true) | .urn'
"urn:pulumi:prod::infra::aws:s3/bucket:Bucket::assets"

The resource is protected; Pulumi refuses to replace it and the update cannot proceed.

6. Importing an existing resource without import semantics

You want Pulumi to adopt an existing object, but the program uses a plain new declaration instead of pulumi import or an import resource option, so Pulumi tries to create rather than adopt.

pulumi up --yes 2>&1 | grep -i 'already'
error: creating ... already exists; use `pulumi import` to adopt the existing resource

Pulumi explicitly tells you the object must be imported, not created.

Diagnostic Workflow

Step 1: Read the exact failing resource and provider error

pulumi up 2>&1 | tail -30

Note the resource type, logical name, and the physical name/ID in the provider error (e.g. my-bucket-name). That physical ID drives every later step.

Step 2: Check whether the resource is in state

pulumi stack export | jq '.deployment.resources[] | select(.type=="aws:s3/bucket:Bucket") | {urn, id}'

If the physical ID is absent from state but present in the cloud, this is an out-of-band or import case (causes 1 and 6).

Step 3: Look for interrupted updates

pulumi stack export | jq '.deployment.pending_operations'
pulumi cancel

A non-empty pending_operations means a prior run was interrupted. pulumi cancel clears the lock so you can re-run safely.

Step 4: Reconcile drift with refresh

pulumi refresh --diff

This re-reads the real cloud state into Pulumi. It surfaces resources that were deleted-in-state-but-live, or properties that drifted out-of-band.

Step 5: Adopt, delete, or unprotect as appropriate

# Adopt an existing object into state instead of creating it:
pulumi import aws:s3/bucket:Bucket assets my-bucket-name

# Or drop a stale/duplicate entry from state (does NOT touch the cloud):
pulumi state delete 'urn:pulumi:prod::infra::aws:s3/bucket:Bucket::assets'

# Or remove protection so the update can proceed:
pulumi state unprotect 'urn:pulumi:prod::infra::aws:s3/bucket:Bucket::assets'

Then re-run pulumi up.

Example Root Cause Analysis

A CI pipeline running pulumi up for stack prod fails on every run with BucketAlreadyOwnedByYou for my-bucket-name. Retrying does not help.

First, confirm the bucket is missing from state but live in AWS:

pulumi stack export | jq '.deployment.resources[].id' | grep my-bucket-name
aws s3api head-bucket --bucket my-bucket-name && echo "exists in AWS"
(no output from the jq filter)
exists in AWS

The bucket exists in AWS but Pulumi has no record of it. Checking the git history shows the bucket was created last quarter with the AWS console during an incident, before it was ever added to the Pulumi program. Now that the code declares it, Pulumi plans a fresh create that collides.

The correct fix is to adopt the existing object into state rather than recreate it:

pulumi import aws:s3/bucket:Bucket assets my-bucket-name
Importing (prod):
 =  aws:s3:Bucket  assets  imported
Resources:
    = 1 imported

warning: aws:s3/bucket:Bucket has been deprecated... (review generated code)

Pulumi prints the resource definition it expects in code; align the program with it, then re-run:

pulumi up --yes

The bucket is now tracked in state, the create becomes a no-op, and the update succeeds.

Prevention Best Practices

  • Treat the cloud console as read-only for Pulumi-managed accounts. Out-of-band creates are the single most common cause of already exists. Codify all changes; see our infrastructure-as-code guides.
  • Run pulumi refresh on a schedule (or in CI before up) so drift surfaces as a diff instead of a failed update.
  • Never kill -9 or hard-cancel a running pulumi up. If a run is interrupted, run pulumi cancel and inspect pending_operations before retrying.
  • Keep physical names (bucket, name) unique across the program, or let Pulumi auto-name resources to avoid duplicate-name collisions.
  • Adopt pre-existing infrastructure with pulumi import (or pulumi import --from) rather than declaring it with new and hoping the apply reconciles.
  • When triaging a failed pulumi up log under pressure, the free incident assistant can summarize the diagnostic into the likely state-vs-cloud cause.

Quick Command Reference

# See the full failure with the physical ID
pulumi up 2>&1 | tail -30

# Is the resource in state?
pulumi stack export | jq '.deployment.resources[] | {urn, id}'

# Check for an interrupted update and clear the lock
pulumi stack export | jq '.deployment.pending_operations'
pulumi cancel

# Reconcile state with the real cloud
pulumi refresh --diff

# Adopt an existing object instead of creating it
pulumi import aws:s3/bucket:Bucket assets my-bucket-name

# Remove a stale/duplicate entry from state (cloud untouched)
pulumi state delete '<URN>'

# Allow replacement/deletion of a protected resource
pulumi state unprotect '<URN>'

# Re-run the update
pulumi up --yes

Conclusion

A Pulumi update failed with resource already exists means the program wants to create an object that already exists in the cloud but is absent from (or stale in) Pulumi state. The usual root causes:

  1. The resource was created out-of-band and never imported into state.
  2. A previous update was interrupted after the cloud object was created.
  3. Two resources resolve to the same physical name (duplicate name).
  4. State drifted from the real cloud and a refresh is overdue.
  5. A protect flag blocks the replacement the plan requires.
  6. An existing resource was declared with new instead of being imported.

Reconcile state with reality first — pulumi refresh, then pulumi import to adopt or pulumi state delete to prune — rather than retrying pulumi up against a colliding object.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.