Skip to content
GitHubXDiscordRSS

Continuous Integration

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 }}
    cleanup:
    runs-on: ubuntu-latest
    if: ${{ github.event.action == 'closed' && github.ref != 'refs/heads/main' }}
    permissions:
    id-token: write
    contents: read
    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 }}