Skip to content
GitHubXDiscordRSS

Continuous Integration

Set up CI/CD pipelines for Alchemy projects with GitHub Actions, automated deployments, and PR previews.

Set up preview deployments and continuous integration for your Alchemy projects using GitHub Actions.

As part of this guide, we’ll:

  1. Add a Github Workflow to deploy your prod stage from the main branch
  2. Deploy a preview pr-<number> stage for each Pull Request
  3. Update your alchemy.run.ts script to add a Github Comment to the PR with the preview URL
  1. Configure environment variables

    Set up required secrets in your GitHub repository settings (Settings → Secrets and variables → Actions):

    Terminal window
    ALCHEMY_PASSWORD=your-encryption-password
    ALCHEMY_STATE_TOKEN=your-state-token
    CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
    CLOUDFLARE_EMAIL=your-cloudflare-email
  2. Set up preview environments in your Alchemy script

    Update your alchemy.run.ts to support multiple stages, use the CloudflareStateStore and add a GithubComment to the PR with the preview URL:

    import alchemy from "alchemy";
    import { Worker, Vite } from "alchemy/cloudflare";
    import { GitHubComment } from "alchemy/github";
    import { CloudflareStateStore } from "alchemy/state";
    const app = await alchemy("my-app", {
    stateStore: (scope) => new CloudflareStateStore(scope),
    });
    // your website may be different, we use Vite for illustration purposes
    const website = await Vite("website");
    console.log(`🚀 Deployed to: https://${website.url}`);
    if (process.env.PULL_REQUEST) {
    // if this is a PR, add a comment to the PR with the preview URL
    // it will auto-update with each push
    await GitHubComment("preview-comment", {
    owner: "your-username",
    repository: "your-repo",
    issueNumber: Number(process.env.PULL_REQUEST),
    body: `
    ## 🚀 Preview Deployed
    Your changes have been deployed to a preview environment:
    **🌐 Website:** ${website.url}
    Built from commit ${process.env.GITHUB_SHA?.slice(0, 7)}
    ---
    <sub>🤖 This comment updates automatically with each push.</sub>`,
    });
    }
    await app.finalize();
  3. Create deployment workflow

    Create .github/workflows/deploy.yml with a workflow for deploying your prod stage from the main branch and a preview pr-<number> stage for each Pull Request:

    name: Deploy Application
    on:
    push:
    branches:
    - main
    pull_request:
    types:
    - opened
    - reopened
    - synchronize
    - closed
    concurrency:
    group: deploy-${{ github.ref }}
    cancel-in-progress: false
    env:
    STAGE: ${{ github.ref == 'refs/heads/main' && 'prod' || format('pr-{0}',
    github.event.number) }}
    jobs:
    deploy:
    if: ${{ github.event.action != 'closed' }}
    runs-on: ubuntu-latest
    permissions:
    contents: read
    pull-requests: write
    steps:
    - uses: actions/checkout@v4
    - name: Setup Bun
    uses: oven-sh/setup-bun@v2
    - name: Install dependencies
    run: bun install
    - name: Deploy
    run: bun alchemy deploy --stage ${{ env.STAGE }}
    env:
    ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}
    ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }}
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
    PULL_REQUEST: ${{ github.event.number }}
    GITHUB_SHA: ${{ github.sha }}
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    cleanup:
    runs-on: ubuntu-latest
    if: ${{ github.event_name == 'pull_request' && github.event.action == 'closed'
    }}
    permissions:
    id-token: write
    contents: read
    pull-requests: write
    steps:
    - uses: actions/checkout@v4
    - name: Setup Bun
    uses: oven-sh/setup-bun@v2
    - name: Install dependencies
    run: bun install
    - name: Destroy Preview Environment
    run: bun alchemy destroy --stage ${{ env.STAGE }}
    env:
    ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}
    ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }}
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
    PULL_REQUEST: ${{ github.event.number }}