Part 5: CI/CD
Keep state in Cloudflare
Section titled “Keep state in Cloudflare”CI runs start from an empty disk, so store state where every run can read it:
{ providers: Layer.mergeAll(Cloudflare.providers(), E2B.providers()), state: Alchemy.localState(), state: Cloudflare.state(), },Store the key
Section titled “Store the key”Add your E2B API key to the repository’s secrets as E2B_API_KEY. Alchemy reads it instead of a profile.
Deploy per pull request
Section titled “Deploy per pull request”name: Deployon: pull_request: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest env: E2B_API_KEY: ${{ secrets.E2B_API_KEY }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} STAGE: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || 'prod' }} steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install - run: bun alchemy deploy --stage "$STAGE" --yesEach pull request gets its own stage. E2B has no keyless CI login, so the workflow uses the API key.
Clean up closed pull requests
Section titled “Clean up closed pull requests”name: Cleanupon: pull_request: types: [closed]
jobs: destroy: runs-on: ubuntu-latest env: E2B_API_KEY: ${{ secrets.E2B_API_KEY }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install - run: bun alchemy destroy --stage "pr-${{ github.event.number }}" --yesDestroying the stage kills the sandboxes its Worker created.