Skip to content

Part 5: CI/CD

Hand the stack to GitHub Actions: pushes to main deploy to prod, pull requests get their own stage, and closing a pull request destroys it.

A runner can’t see your .alchemy/ directory, and Archil has no state store of its own. Use a remote one, such as a Postgres database you already run (install @alchemy.run/sql and @effect/sql-pg):

alchemy.run.ts
import { postgresState } from "@alchemy.run/sql/PostgresState";
import * as Config from "effect/Config";
export default Alchemy.Stack(
"MyApp",
{ providers: Archil.providers(), state: Alchemy.localState() },
{
providers: Archil.providers(),
state: postgresState({ url: Config.Redacted("STATE_DATABASE_URL") }),
},

Cloudflare.state(), AWS.state() and GCP.state() work too if you already use that cloud. See State stores.

Archil creates API keys only in the console, so CI uses one you create there. Create a key for CI under API Keys, then store it and the state database URL as repository secrets:

Terminal window
gh secret set ARCHIL_API_KEY
gh secret set STATE_DATABASE_URL

Archil.providers() reads ARCHIL_API_KEY (and ARCHIL_REGION, if set) from the environment when no profile is selected.

.github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
pull_request:
types: [opened, reopened, synchronize, closed]
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
env:
STAGE: >-
${{ github.event_name == 'pull_request'
&& format('pr-{0}', github.event.number)
|| 'prod' }}
ARCHIL_API_KEY: ${{ secrets.ARCHIL_API_KEY }}
STATE_DATABASE_URL: ${{ secrets.STATE_DATABASE_URL }}
jobs:
deploy:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun alchemy deploy --stage ${{ env.STAGE }} --yes
cleanup:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: Refuse to destroy prod
run: test "${{ env.STAGE }}" != "prod"
- run: bun alchemy destroy --stage ${{ env.STAGE }} --yes

Pull requests deploy to pr-<number>, and main deploys to prod. The cleanup job refuses to destroy prod.

Archil can’t scope or expire API keys. To rotate one, create a new key in the console, run gh secret set ARCHIL_API_KEY again, redeploy, then delete the old key. Hosts that call Archil’s control plane pick up the new key on that deploy; hosts that only use disks hold their own disk users and aren’t affected.

  • A remote state store lets every runner see the same deploys.
  • Each pull request gets its own stage, destroyed when it closes.
  • The Archil key lives in a repository secret.