Skip to content

Adopting Resources

Two situations put alchemy in front of a cloud resource it has no state for: existing infrastructure you want alchemy to start managing, and a fresh (or wiped) state store sitting in front of resources alchemy already created.

Terminal window
bun alchemy deploy --adopt

When a resource has no prior state, the engine calls the provider’s read to check whether the resource already exists in the cloud. The return value drives one of three paths:

read returnsWithout --adoptWith --adopt
undefinedcreatecreate
owned (plain attrs)silent adoptsilent adopt
Unowned(attrs)fail OwnedBySomeoneElsetake over (silently)

“Owned” means the provider can prove the resource was created by this stack/stage/logical-id — typically by inspecting tags or a naming convention.

The same table drives the provider-side contract: see Adoption in Resource Lifecycle for the engine’s routing and the read operation for how a provider returns Unowned(attrs).

Re-importing owned resources into a fresh state store needs no flag — recovering a wiped state store for resources you already own is the default behavior.

--adopt only matters when the provider reports a resource exists but isn’t ours. That’s the deliberately load-bearing case: by default alchemy refuses to silently overwrite tags/config of a resource it can’t prove ownership of. --adopt says “yes, take it over.”

Terminal window
# Re-import existing alchemy-tagged resources into a fresh state
# store — no flag needed for these. Ones with foreign ownership tags
# will surface as `OwnedBySomeoneElse` errors.
alchemy deploy
# Force takeover of any conflicting resource regardless of tags.
alchemy deploy --adopt

--adopt opts the entire deploy in to adoption-on-conflict — it is provided as a single AdoptPolicy layer over the whole stack execution, not per resource.

The default is false so an unintentional collision still surfaces loudly instead of silently overwriting someone else’s resource. The option is part of the shared ExecStackOptions that every stack-executing command (including dev) runs through.

When adoption is refused: OwnedBySomeoneElse

Section titled “When adoption is refused: OwnedBySomeoneElse”

When read reports an existing resource as Unowned and adoption is off, the deploy fails with the typed OwnedBySomeoneElse error, carrying message, resourceType, logicalId, and physicalName.

You have three ways out: re-tag the cloud resource so the provider recognizes it as yours, pick a different physical name so there is no collision, or re-run with --adopt (or adopt(true)) to force a takeover.

You can also enable adoption programmatically by piping the adopt combinator onto a deploy effect from alchemy/AdoptPolicy:

import { adopt } from "alchemy/AdoptPolicy";
yield* deploy(
Effect.gen(function* () {
yield* Cloudflare.Worker("API", { /* ... */ });
}),
).pipe(adopt(true));

AdoptPolicy is consulted at plan time, so the combinator must wrap the deploy (or test runner’s stack.deploy(...)) — not the inner resource-declaration effect.

adopt() with no argument means true, and it also accepts an Effect<boolean> for dynamic policies:

deployEffect.pipe(adopt()); // same as adopt(true)
deployEffect.pipe(adopt(decidePolicy)); // Effect<boolean>, resolved at plan time

Providers with no tag concept (e.g. APIs that always return a singleton by name) always return plain attrs from read, so they silently adopt unconditionally — --adopt is a no-op for them.

For the full lost-state-store recovery walkthrough (state tree → state clear → deploy), see Inspecting State.