Self-hosting
The code is open-source, so you can self-host it if you want to (e.g. to run on a private network, or to use it without sponsoring me, or to use a different blob storage provider, or to add extra features etc.). Here’s how:
- Clone the repository.
- Deploy to Cloudflare via Alchemy.
pnpm deploy:dev(orpnpm deploy:prod) stands up the three Workers (app,docs,frontdoor) in your account, and creates the D1 database and R2 bucket the app uses. - GitHub App. Set up an App at https://github.com/settings/apps.
- Set the callback URL to
https://<your-domain>/api/auth/callback/github. - Add the following environment variables to your
.env.<stage>file:GITHUB_APP_IDandGITHUB_APP_PRIVATE_KEY— needed for the App to make GitHub API requests.GITHUB_APP_WEBHOOK_SECRET— needed to verify webhook requests really came from GitHub.GITHUB_APP_CLIENT_IDandGITHUB_APP_CLIENT_SECRET— needed for the OAuth login flow.GITHUB_APP_URL.
- Set the callback URL to
- Auth setup:
- Add a
BETTER_AUTH_SECRETto your.env.<stage>file.
- Add a
- Blob storage setup:
- This project uses Cloudflare R2 for artifact blobs. Alchemy creates a stage-scoped bucket (
artifact-ci-<stage>-blobs) on first deploy. - The app Worker mints S3-style presigned PUT URLs so the browser can upload directly to R2. That requires an R2 API token: open the Cloudflare dashboard → R2 → Manage R2 API Tokens → create a token with Object Read & Write scoped to the bucket, and add
R2_ACCESS_KEY_IDandR2_SECRET_ACCESS_KEYto.env.<stage>. (You’ll need to deploy once first so the bucket exists, then mint the token, then deploy again so the worker picks up the keys.) alchemy.run.tsconfigures CORS allow-origins on the bucket so browsers can PUT to presigned URLs from your site. If you serve the app from a different origin, update thecorsrule inalchemy.run.tsto match.
- This project uses Cloudflare R2 for artifact blobs. Alchemy creates a stage-scoped bucket (
- Database setup:
- This project uses Cloudflare D1 for metadata. Alchemy creates a stage-scoped database (
artifact-ci-<stage>-db) on first deploy. - Schema is applied via the SQL files in
migrations/. Alchemy runs them automatically against the D1 database during deploy — no manual step required for a fresh setup. - The client uses
sqlfu: if you change database queries, runpnpm exec sqlfu generateto regenerate query types, andpnpm exec sqlfu draft --name <description>to scaffold a new migration.
- This project uses Cloudflare D1 for metadata. Alchemy creates a stage-scoped database (
- You’ll need to manage the
usage_creditstable to whitelist your organization/users to make sure they aren’t denied access to artifacts. - Local development:
- Run
pnpm devto start the development server. - The dev script spins up a Cloudflare quick tunnel so GitHub webhooks can reach your laptop. The tunnel URL is printed at startup.
- The local R2 binding talks to real R2 (miniflare’s local R2 isn’t S3-compatible, so signed PUT URLs can’t reach it). That requires
wrangler loginonce so the Cloudflare vite plugin can establish a remote-proxy session. Dev and prod use different stage-scoped buckets, so dev never touches prod data. - Note that simulating a GitHub Actions workflow is tricky. The API checks that the workflow is in “running” status before minting artifact upload tokens.
- Run