Continuous Integration
Set up preview deployments and continuous integration for your Alchemy projects using GitHub Actions.
As part of this guide, we’ll:
- Add a Github Workflow to deploy your
prod
stage from themain
branch - Deploy a preview
pr-<number>
stage for each Pull Request - Update your
alchemy.run.ts
script to add a Github Comment to the PR with the preview URL
-
Configure environment variables
Set up required secrets in your GitHub repository settings (Settings → Secrets and variables → Actions):
Terminal window ALCHEMY_PASSWORD=your-encryption-passwordALCHEMY_STATE_TOKEN=your-state-tokenCLOUDFLARE_API_TOKEN=your-cloudflare-api-tokenCLOUDFLARE_EMAIL=your-cloudflare-email -
Create deployment workflow
Create
.github/workflows/deploy.yml
with a workflow for deploying yourprod
stage from themain
branch and a previewpr-<number>
stage for each Pull Request:name: Deploy Applicationon:push:branches: [main]pull_request:types: [opened, reopened, synchronize, closed]concurrency:group: "deploy-${{ github.ref }}"cancel-in-progress: falseenv:STAGE: ${{ github.ref == 'refs/heads/main' && 'prod' || format('pr-{0}', github.event.number) }}jobs:deploy:# only deploy for open PRsif: ${{ github.event.action != 'closed' }}runs-on: ubuntu-latestpermissions:contents: readpull-requests: writesteps:- uses: actions/checkout@v4- name: Setup Bunuses: oven-sh/setup-bun@v2- name: Install dependenciesrun: bun install- name: Deployrun: 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:# cleanup when the PR is closedruns-on: ubuntu-latestif: ${{ github.event.action == 'closed' && github.ref != 'refs/heads/main' }}permissions:id-token: writecontents: readsteps:- uses: actions/checkout@v4- name: Setup Bunuses: oven-sh/setup-bun@v2- name: Install dependenciesrun: bun install- name: Destroy Preview Environmentrun: 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 }} -
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 purposesconst website = await Vite("website", {// publish a version of the Website's Worker if this is a non-prod stageversion: app.stage === "prod" ? undefined : app.stage,});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 pushawait GitHubComment("preview-comment", {owner: "your-username",repository: "your-repo",issueNumber: Number(process.env.PULL_REQUEST),body: `## 🚀 Preview DeployedYour 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();