Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/alchemy-run/alchemy/llms.txt

Use this file to discover all available pages before exploring further.

Deployment

Alchemy makes it simple to deploy infrastructure to production. This guide covers deployment strategies, best practices, and CI/CD integration.

Basic Deployment

Deploy your application by running your alchemy.run.ts script:
bun ./alchemy.run.ts
This will:
  1. Create or update resources
  2. Save state to .alchemy/
  3. Display resource URLs and outputs

Deployment Phases

Alchemy supports three phases:

Up Phase (Default)

Create or update resources:
bun ./alchemy.run.ts
# Or explicitly:
bun ./alchemy.run.ts --stage production

Read Phase

Read resource state without making changes:
bun ./alchemy.run.ts --read
Use read mode to inspect current infrastructure state.

Destroy Phase

Delete all resources:
bun ./alchemy.run.ts --destroy
This will permanently delete all resources in the current stage. Use with caution.

Stages

Stages allow you to deploy multiple independent environments (dev, staging, production):

Using Stages

# Development stage (default: your username)
bun ./alchemy.run.ts

# Staging stage
bun ./alchemy.run.ts --stage staging

# Production stage
bun ./alchemy.run.ts --stage production
Each stage has isolated state:
.alchemy/
  my-app/
    john/           # Development (your username)
      state.json
    staging/
      state.json
    production/
      state.json

Environment-Specific Configuration

Use different configurations per stage:
alchemy.run.ts
import alchemy from "alchemy";
import { Worker } from "alchemy/cloudflare";

const app = await alchemy("my-app");
const isProd = app.stage === "production";

const worker = await Worker("api", {
  entrypoint: "./src/index.ts",
  bindings: {
    LOG_LEVEL: isProd ? "error" : "debug",
    API_URL: isProd 
      ? "https://api.example.com"
      : "https://staging-api.example.com"
  }
});

console.log(`Deployed to: ${worker.url}`);

await app.finalize();

State Management

Local State (Default)

By default, Alchemy stores state locally in .alchemy/:
.alchemy/
  my-app/
    production/
      state.json      # Resource state
Local state is fine for development, but use remote state stores for CI/CD and teams.

Remote State

For production and teams, use a remote state store:
import { CloudflareStateStore } from "alchemy/state";

const app = await alchemy("my-app", {
  stateStore: (scope) => new CloudflareStateStore(scope)
});
When running in CI, Alchemy requires a remote state store. Set ALCHEMY_CI_STATE_STORE_CHECK=false to disable this check (not recommended).

Secrets Management

Setting Passwords

Secrets require a password for encryption:
export ALCHEMY_PASSWORD="your-secure-password"
bun ./alchemy.run.ts
Or set it programmatically:
const app = await alchemy("my-app", {
  password: process.env.ALCHEMY_PASSWORD
});

Per-Stage Passwords

Use different passwords for different stages:
# Development
export ALCHEMY_PASSWORD="dev-password"
bun ./alchemy.run.ts --stage dev

# Production
export ALCHEMY_PASSWORD="prod-password"
bun ./alchemy.run.ts --stage production

CI/CD Integration

GitHub Actions

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: oven-sh/setup-bun@v2
      
      - name: Install dependencies
        run: bun install
      
      - name: Deploy to production
        env:
          ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}
          CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
        run: bun ./alchemy.run.ts --stage production

GitLab CI

.gitlab-ci.yml
stages:
  - deploy

deploy:production:
  stage: deploy
  image: oven/bun:latest
  only:
    - main
  script:
    - bun install
    - bun ./alchemy.run.ts --stage production
  variables:
    ALCHEMY_PASSWORD: $ALCHEMY_PASSWORD
    CLOUDFLARE_API_KEY: $CLOUDFLARE_API_KEY
    CLOUDFLARE_ACCOUNT_ID: $CLOUDFLARE_ACCOUNT_ID

CircleCI

.circleci/config.yml
version: 2.1

jobs:
  deploy:
    docker:
      - image: oven/bun:latest
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: bun install
      - run:
          name: Deploy
          command: bun ./alchemy.run.ts --stage production

workflows:
  deploy:
    jobs:
      - deploy:
          filters:
            branches:
              only: main

Deployment Strategies

Blue-Green Deployment

Deploy to a new stage, test, then switch:
1
Deploy to Green Stage
2
bun ./alchemy.run.ts --stage green
3
Test Green Stage
4
curl https://green-my-app.workers.dev/health
5
Switch Traffic
6
Update DNS or routing to point to green stage.
7
Destroy Blue Stage
8
bun ./alchemy.run.ts --destroy --stage blue

Canary Deployment

Gradually roll out changes:
const app = await alchemy("my-app");

// Deploy canary version
if (process.argv.includes("--canary")) {
  const canary = await Worker("api-canary", {
    entrypoint: "./src/index.ts",
    routes: [{ pattern: "api.example.com/canary/*" }]
  });
}

// Main version
const main = await Worker("api", {
  entrypoint: "./src/index.ts",
  routes: [{ pattern: "api.example.com/*" }]
});

await app.finalize();

Rollback

Quick Rollback

Destroy the current deployment and redeploy a previous version:
# Destroy current deployment
bun ./alchemy.run.ts --destroy --stage production

# Check out previous version
git checkout <previous-commit>

# Redeploy
bun ./alchemy.run.ts --stage production

State-Based Rollback

Alchemy maintains state history. You can manually restore previous state:
# Backup current state
cp .alchemy/my-app/production/state.json .alchemy/my-app/production/state.json.backup

# Restore previous state
cp .alchemy/my-app/production/state.json.previous .alchemy/my-app/production/state.json

# Apply
bun ./alchemy.run.ts --stage production

Monitoring Deployments

Deployment Logs

Alchemy logs all resource operations:
Create Worker "my-app-production-api"
Update D1Database "my-app-production-db"
Delete R2Bucket "old-bucket"

Quiet Mode

Suppress logs in CI:
bun ./alchemy.run.ts --quiet --stage production
Or programmatically:
const app = await alchemy("my-app", {
  quiet: true
});

Telemetry

Alchemy sends anonymous telemetry by default. Opt out:
export ALCHEMY_TELEMETRY_DISABLED=1
bun ./alchemy.run.ts
Or:
const app = await alchemy("my-app", {
  noTrack: true
});

Best Practices

1
Use Remote State in CI
2
Never use local state in CI/CD:
3
import { CloudflareStateStore } from "alchemy/state";

const app = await alchemy("my-app", {
  stateStore: (scope) => new CloudflareStateStore(scope)
});
4
Separate Stages
5
Use dedicated stages for each environment:
6
# Development
bun ./alchemy.run.ts --stage dev

# Staging
bun ./alchemy.run.ts --stage staging

# Production
bun ./alchemy.run.ts --stage production
7
Store Secrets Securely
8
Use GitHub Secrets, AWS Secrets Manager, or similar:
9
env:
  ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}
  CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}
10
Test Before Production
11
Always test in staging first:
12
jobs:
  deploy-staging:
    steps:
      - run: bun ./alchemy.run.ts --stage staging
      - run: bun test
  
  deploy-production:
    needs: deploy-staging
    steps:
      - run: bun ./alchemy.run.ts --stage production
13
Use Consistent Naming
14
Follow a naming convention:
15
const app = await alchemy("my-company-my-app");
// Resources: my-company-my-app-production-api
16
Enable Adoption for Existing Resources
17
When migrating to Alchemy:
18
const app = await alchemy("my-app", {
  adopt: true  // Adopt existing resources
});

Multi-Region Deployment

Deploy to multiple regions:
import alchemy from "alchemy";
import { Worker } from "alchemy/cloudflare";

const regions = ["us-east", "us-west", "eu-central"];

for (const region of regions) {
  const app = await alchemy(`my-app-${region}`, {
    stage: "production"
  });
  
  const worker = await Worker(`api-${region}`, {
    entrypoint: "./src/index.ts",
    bindings: {
      REGION: region
    }
  });
  
  console.log(`${region}: ${worker.url}`);
  
  await app.finalize();
}

Troubleshooting

State Conflicts

If multiple deployments run simultaneously:
Error: State conflict detected
Solution: Use a state store with locking (CloudflareStateStore, S3StateStore).

Missing Credentials

Error: CLOUDFLARE_API_KEY is required
Ensure all required environment variables are set:
export CLOUDFLARE_API_KEY=your-key
export CLOUDFLARE_ACCOUNT_ID=your-id

Orphaned Resources

If resources aren’t cleaned up:
bun ./alchemy.run.ts --force --stage production
This forces a full reconciliation.

Example: Production Deployment

import "dotenv/config";
import alchemy from "alchemy";
import { Worker, D1Database, R2Bucket } from "alchemy/cloudflare";
import { CloudflareStateStore } from "alchemy/state";
import path from "node:path";

// Production-ready configuration
const app = await alchemy("my-app", {
  // Use remote state in production
  stateStore: (scope) => new CloudflareStateStore(scope),
  // Require password for secrets
  password: process.env.ALCHEMY_PASSWORD,
  // Disable telemetry if needed
  noTrack: process.env.ALCHEMY_TELEMETRY_DISABLED === "1"
});

// Infrastructure
const db = await D1Database("database", {
  name: `${app.appName}-${app.stage}-db`
});

const bucket = await R2Bucket("storage", {
  name: `${app.appName}-${app.stage}-storage`
});

// Application
const worker = await Worker("api", {
  entrypoint: path.join(import.meta.dirname, "src", "index.ts"),
  bindings: {
    DB: db,
    BUCKET: bucket,
    API_KEY: alchemy.secret.env.API_KEY,
    LOG_LEVEL: app.stage === "production" ? "error" : "debug"
  }
});

console.log(`
Deployment complete!

Stage: ${app.stage}
Worker: ${worker.url}
Database: ${db.name}
Bucket: ${bucket.name}
`);

await app.finalize();

Next Steps

Build docs developers (and LLMs) love