Skip to content

CLI Reference

The Alchemy CLI manages the lifecycle of your stacks. Every command operates on an alchemy.run.ts file (or a custom entrypoint) and targets a stage — an isolated environment like dev_sam, prod, or pr-42.

Terminal window
alchemy <command> [file] [options]

If no file is specified, the CLI looks for alchemy.run.ts in the current directory.

These options are shared across most commands:

OptionDescription
--stage <name>Stage to target. Defaults to dev_$USER (e.g. dev_sam). Must match [a-z0-9][-_a-z0-9]*.
--env-file <path>Load environment variables from a file before running.

Compute a plan, ask for approval, and create/update/delete resources to match the desired state.

Terminal window
alchemy deploy [file] [options]
Plan: 2 to create

+ Bucket (Cloudflare.R2Bucket)
+ Worker (Cloudflare.Worker) (1 bindings)
  + Bucket

Proceed?
◉ Yes ○ No
 Bucket (Cloudflare.R2Bucket) created
 Worker (Cloudflare.Worker) created
  • Uploading worker (14.20 KB) ...
  • Enabling workers.dev subdomain...
{
  url: "https://myapp-worker-dev-you-abc123.workers.dev",
}

On subsequent deploys, only changed resources are updated:

Plan: 1 to update

~ Worker (Cloudflare.Worker)

Proceed?
◉ Yes ○ No
 Worker (Cloudflare.Worker) updated
  • Uploading worker (15.10 KB) ...
{
  url: "https://myapp-worker-dev-you-abc123.workers.dev",
}
OptionDescription
--stage <name>Stage to deploy to (defaults to dev_$USER)
--yesSkip the approval prompt
--dry-runShow the plan without applying (same as alchemy plan)
--forceForce updates for resources that would otherwise no-op
--adoptAdopt pre-existing cloud resources that conflict with this stack instead of failing. Useful for re-importing into a fresh state store.
--profile <name>Auth profile to use (defaults to default or $ALCHEMY_PROFILE)
--env-file <path>Load environment variables from a file
Terminal window
# Deploy to production, skip the prompt
alchemy deploy --stage prod --yes
# Deploy a different stack file
alchemy deploy stacks/github.ts
# Preview what would change
alchemy deploy --dry-run
# Re-import existing cloud resources into a fresh state store
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. Recovering a wiped state store for resources you already own is the default behavior: no flag required.

--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

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.

Resources without ownership semantics (e.g. APIs that always return a singleton by name, with no tag concept) silently adopt unconditionally — --adopt is a no-op for them.

Preview what would change without applying anything. Equivalent to alchemy deploy --dry-run.

Terminal window
alchemy plan [file] [options]
Plan: 1 to create, 1 to update

+ Queue (AWS.SQS.Queue)
~ Worker (Cloudflare.Worker)

The plan uses + for creates, ~ for updates, - for deletes, and for no-ops. No approval prompt is shown and no changes are made.

OptionDescription
--stage <name>Stage to plan against (defaults to dev_$USER)
--env-file <path>Load environment variables from a file

Delete every resource in a stack. Computes a plan where all existing resources are marked for deletion, asks for approval, and removes them in dependency order.

Terminal window
alchemy destroy [file] [options]
Plan: 2 to delete

- Worker (Cloudflare.Worker)
- Bucket (Cloudflare.R2Bucket)

Proceed?
◉ Yes ○ No
 Worker (Cloudflare.Worker) deleted
 Bucket (Cloudflare.R2Bucket) deleted
OptionDescription
--stage <name>Stage to destroy (defaults to dev_$USER)
--yesSkip the approval prompt
--dry-runShow what would be deleted without actually deleting
--env-file <path>Load environment variables from a file
Terminal window
# Destroy a PR preview environment
alchemy destroy --stage pr-42 --yes

Run your stack in development mode with hot reloading.

Terminal window
alchemy dev [file] [options]

Resources are deployed to the cloud while Workers run locally in workerd. File changes trigger automatic rebuilds and hot reloads.

OptionDescription
--stage <name>Stage to use for dev (defaults to dev_$USER)
--env-file <path>Load environment variables from a file
Terminal window
# Start dev mode
alchemy dev
# Use a custom stage
alchemy dev --stage dev

Stream live logs from deployed resources in real time.

Terminal window
alchemy tail [file] [options]
Tailing: Worker, Api

2026-04-15 14:32:01.123 PST [Worker] GET /hello.txt 200
2026-04-15 14:32:01.456 PST [Worker] PUT /world.txt 201
2026-04-15 14:32:02.789 PST [Api] POST /api/data 200

Logs from multiple resources are interleaved and color-coded by resource. The command streams indefinitely until you interrupt it with Ctrl+C.

OptionDescription
--stage <name>Stage to tail (defaults to dev_$USER)
--filter <ids>Comma-separated logical resource IDs to include (e.g. Worker,Api)
--env-file <path>Load environment variables from a file
Terminal window
# Tail only the Worker resource
alchemy tail --filter Worker
# Tail a specific stage
alchemy tail --stage prod

Fetch historical logs from deployed resources.

Terminal window
alchemy logs [file] [options]

Unlike tail, logs fetches a batch of past log entries and exits.

OptionDescription
--stage <name>Stage to fetch logs from (defaults to dev_$USER)
--filter <ids>Comma-separated logical resource IDs to include
--limit <n>Number of log entries to fetch (default: 100)
--since <time>Fetch logs since this time — a duration (1h, 30m, 2d) or ISO date
--env-file <path>Load environment variables from a file
Terminal window
# Last 50 log entries from all resources
alchemy logs --limit 50
# Logs from the last hour, Worker only
alchemy logs --filter Worker --since 1h
# Logs from a specific stage since a date
alchemy logs --stage prod --since 2026-04-01T00:00:00Z

Set up the per-cloud infrastructure that Alchemy itself relies on — the AWS assets bucket used for Lambda artifacts, or the Cloudflare state-store worker that holds your remote stack state.

Terminal window
alchemy bootstrap <provider> [options]

The bootstrap command is split into provider-specific subcommands. Run alchemy bootstrap <provider> --help to see the flags for each.

Set up the AWS assets bucket required for deploying Lambda functions and other AWS resources that need artifact storage.

Terminal window
alchemy bootstrap aws [options]
OptionDescription
--profile <name>AWS profile to use for credentials (default: default)
--region <region>AWS region to bootstrap (defaults to AWS_REGION env var)
--destroyDestroy all bootstrap buckets in the selected region
--env-file <path>Load environment variables from a file
Terminal window
# Bootstrap with the default profile
alchemy bootstrap aws
# Bootstrap a specific region and profile
alchemy bootstrap aws --profile prod --region us-west-2
# Remove bootstrap resources
alchemy bootstrap aws --destroy

Manually deploy (or repair) the Cloudflare-hosted HTTP State Store — the worker + Secrets Store + auth-token secret that back the remote-state layer used by Cloudflare.state(...).

You normally don’t need to run this: the very first stack deploy that uses Cloudflare.state(...) will prompt you to bootstrap automatically. Use this command to re-run that flow on demand — typically to recover from a previous deploy that was interrupted mid-bootstrap.

Terminal window
alchemy bootstrap cloudflare [options]
OptionDescription
--profile <name>Alchemy auth profile (defaults to default or $ALCHEMY_PROFILE). Determines which ~/.alchemy/profiles.json entry is used.
--forceForce a full redeploy even if the worker already exists. Without this flag, an existing worker is adopted and only its credentials are refreshed.
--worker-name <name>Override the default state-store worker name. Advanced; only needed if you run multiple state stores per Cloudflare account.
--env-file <path>Load environment variables from a file

The bootstrap is idempotent and self-healing:

  • If the worker already exists in your Cloudflare account, it is adopted: the auth token is re-fetched live via a short-lived edge-preview probe (the only way to read a Secrets Store value), and ~/.alchemy/credentials/<profile>/cloudflare-state-store.json is rewritten with the current token.
  • If a previous run failed mid-flight (e.g. the worker got created but credentials never landed on disk), the leftover local state stack is detected and the deploy resumes where it left off. The bootstrap is only considered complete once the local stack has been hoisted into the remote store and removed.
  • With --force, the deploy runs again unconditionally. Existing Cloudflare resources (worker, Secrets Store, secret) are still reconciled in place rather than replaced — adoption is enabled automatically for the bootstrap stack.
Terminal window
# First-time bootstrap (or repair after a failed deploy)
alchemy bootstrap cloudflare
# Bootstrap a separate profile
alchemy bootstrap cloudflare --profile staging
# Force a full redeploy (e.g. to roll out an updated state-store worker)
alchemy bootstrap cloudflare --force

Configure and log in to each cloud provider used by your stack. The command imports your stack file to discover which AuthProviders are registered, then walks through each one — prompting for the auth method the first time, and refreshing tokens (e.g. Cloudflare OAuth) on subsequent runs.

Credentials are written to ~/.alchemy/profiles.json, keyed by profile name (defaults to default, overridable with $ALCHEMY_PROFILE or --profile).

Terminal window
alchemy login [file] [options]
OptionDescription
--profile <name>Profile to write to (defaults to default or $ALCHEMY_PROFILE)
--configureRe-run the provider’s interactive configure step before logging in
--stage <name>Stage used while loading the stack (defaults to dev_$USER)
--env-file <path>Load environment variables from a file
Terminal window
# Log in with the default profile
alchemy login
# Log in to a separate profile
alchemy login --profile prod
# Re-configure (e.g. switch from OAuth to API token) and log in
alchemy login --configure

Inspect credentials stored in ~/.alchemy/profiles.json.

Print every auth method configured under a profile, along with its resolved credentials (redacted). Unlike login, this does not import your stack file — it reads the profile store directly and uses the bundled providers (Cloudflare, AWS) to pretty-print each entry.

Terminal window
alchemy profile show [options]
Profile: default

── AWS ──
accessKeyId:     ASIA****
secretAccessKey: Pj5T****
sessionToken:    IQoJ****
region:          us-west-2
source: sso - default

── Cloudflare ──
accessToken: Xl06****
expires: in 59m 58s 999ms (2026-04-27T20:45:47.937Z)
accountId: 123456789...
source: oauth
OptionDescription
--profile <name>Profile to show (defaults to default or $ALCHEMY_PROFILE)
--env-file <path>Load environment variables from a file
Terminal window
# Show the default profile
alchemy profile show
# Show a named profile
alchemy profile show --profile prod

Inspect and manage the state store — the record of which resources Alchemy thinks exist for each stack/stage. Reads from whatever state layer the stack file configures (e.g. Cloudflare.state(...)), or from the on-disk .alchemy/state directory with --local.

Terminal window
alchemy state <subcommand> [options]

The stack file is imported to resolve its configured state layer, so all subcommands accept the same --stage, --profile, and --env-file options as deploy. Pass [file] via the standard positional script argument inherited from the root command.

OptionDescription
--localRead from local .alchemy/state instead of the stack’s configured state store
--stage <name>Stage used while loading the stack (defaults to dev_$USER)
--profile <name>Auth profile to use (defaults to default or $ALCHEMY_PROFILE)
--env-file <path>Load environment variables from a file

List every stack name present in the state store.

Terminal window
alchemy state stacks [file] [options]

List every stage that has state recorded under <stack>.

Terminal window
alchemy state stages <stack> [file] [options]

List the fully-qualified resource names (FQNs) tracked under a given stack/stage.

Terminal window
alchemy state resources <stack> <stage> [file] [options]

Print a single resource’s persisted state as JSON. Output uses the same encoding the store persists: redacted secrets are unwrapped into { __redacted__: ... } and Resources are flattened.

Terminal window
alchemy state get <stack> <stage> <fqn> [file] [options]

Render the entire state store as a tree of stacks → stages → resources.

Terminal window
alchemy state tree [file] [options]
AlchemyEffectWebsite
├─ dev_sam
│  ├─ Bucket
│  └─ Worker
└─ prod
 ├─ Bucket
 └─ Worker

Delete state entries from the store. Destructive but local-only — the actual cloud resources are not touched, only Alchemy’s record of them. A subsequent deploy will see an empty state and try to re-create everything (use --adopt to reconcile against existing infrastructure).

Terminal window
alchemy state clear [stack] [stage] [file] [options]
  • Omit both arguments to clear all stacks in the store.
  • Pass <stack> to clear every stage under that stack.
  • Pass <stack> <stage> to clear a single stage.
OptionDescription
--yesSkip the confirmation prompt
Terminal window
# Inspect what's in the store
alchemy state tree
# Drop a single PR-preview stage
alchemy state clear myapp pr-42 --yes
# Wipe local state after a botched bootstrap
alchemy state clear --local

Every command targets a stage — an isolated instance of your stack. The stage defaults to dev_$USER (e.g. dev_sam), so each developer gets their own environment automatically.

Terminal window
alchemy deploy --stage prod
alchemy deploy --stage pr-42
alchemy destroy --stage dev_sam

Resources are namespaced by stage. Physical names include the stage (e.g. myapp-prod-bucket-abc123), so environments never interfere with each other.