1---
2title: Optimizing Alchemy's CI Workflows
3date: 2026-09-24T16:00:00Z
4excerpt: Moving CI to Blacksmith, fixing cache bugs, switching to pnpm, running CI conditionally and cut website build times from 162s to 39s.
5---
6
7As of writing this, Alchemy is a monorepo of almost 100 packages (86 of them are [distilled](https://distilled.cloud) SDKs), with a docs website with thousands of generated pages, regular CI checks, preview package publishing, tests for our local cloudflare runtime and a release pipeline that publishes to npm. Over past few weeks we made several CI improvements to make it so the contributors and their agents can iterate fast.
8
9## Swapping out GitHub runners and Tooling
10Our first immediate improvement came from moving our workflows to [Blacksmith](https://blacksmith.sh/?ref=alchemy.run) runners ([#1162](https://github.com/alchemy-run/alchemy/pull/1162)). Just by doing this we saw a huge improvement, let's take a look at some numbers:
11
12| Workflow | Before | After | |
13|---|---:|---:|---:|
14| check | 7.8 min | 4.8 min | −38% |
15| cloudflare-tools | 16.2 min | 5.3 min | −67% |
16| pr-package | 5.0 min | 3.2 min | −36% |
17
18These numbers are compiled from a very short numbers of runs we had immediately after switching, since I started optimizing the CI pipeline soon after we don't have much data to compare, but it's clear that just by switching to Blacksmith made a difference already.
19
20We also switched our package manager from `bun` to `pnpm` ([#1214](https://github.com/alchemy-run/alchemy/pull/1214)). Bun kept us giving trouble: it behaves weirdly around updating lock files when manually add/removing deps from `package.json`, fails randomly during install failures in CI, broke our publishing of packages when using `workspace:` and `catalog:` in dependencies, doesn't respect `publishConfig` overrides, didn't have deduping until `1.4`, doesn't work without `pkg.alchemy.run` preview URLs since it doesn't dedupe tarball url deps, and the list goes on.
21
22We are still supporting and using bun as our runtime, but our repo is stuck on bun `1.3.13` since later versions introduced a [bug in `AsyncLocalStorage`](https://github.com/oven-sh/bun/issues/32693) which breaks our test runner. This is yet to be fixed and our blocker for upgrading `bun`.
23
24All of our CI now runs on Blacksmith, except for **npm releases**. Npm [trusted publishing](https://docs.npmjs.com/trusted-publishers/) only allows publishing from GitHub-hosted runners, GitLab.com, and CircleCI cloud. So we had to split releases in two jobs: build on Blacksmith, publish on GitHub. We don't want this extra complexity, but npm has yet to [allow trusted publishing from any other CI provider](https://x.com/samgoodwin89/status/2085278917825552412).
25
26```yaml
27name: Release
28jobs:
29 build:
30 runs-on: blacksmith-8vcpu-ubuntu-2404
31 # Build, Pack, Upload tarballs
32 publish:
33 needs: build
34 runs-on: ubuntu-latest
35 permissions:
36 id-token: write
37 # Publish tarballs using pnpm publish which handles everything
38```
39
40## Making our website deploy faster
41
42Our docs site is a content heavy site. It used to have over 4k+ pages (and paired OG image for each page), most of them being generated specs from our jsdoc. It would take up to 25 min building and deploying all those pages and assets.
43[Astro 7.2](https://astro.build/blog/astro-720/) shipped [experimental incremental builds](https://docs.astro.build/en/reference/experimental-flags/incremental-build/), which skip building pages that haven't changed. We enabled it, patched Starlight to support it, and persisted the cache across CI runs ([#1128](https://github.com/alchemy-run/alchemy/pull/1128), [#1136](https://github.com/alchemy-run/alchemy/pull/1136)). Now a warm build restores most of the pages and almost all OG images.
44
45But the biggest improvement came from changing how the API references are generated. We had one page per resource totaling to 4,368 pages. We merged them into one page per service ([#1767](https://github.com/alchemy-run/alchemy/pull/1767), [#1774](https://github.com/alchemy-run/alchemy/pull/1774)).
46
47| | Before | After |
48|---|---:|---:|
49| Reference pages | 4,368 | 406 |
50| Cold build | 162 s | 39 s |
51| Output size | 3.64GB | 438MB |
52| Website CI run (median) | 8.0 min | 4.1 min |
53
54Along with that, Website previews in PRs used to deploy a new Worker each time. Now they're versions of a single stable preview Worker, so assets already uploaded from `main` are reused ([#1267](https://github.com/alchemy-run/alchemy/pull/1267)). Deploys on PRs are still only triggered when `deploy-website` tag is used, and doesn't work on fork PRs yet.
55
56## Conditional Test runs, workflow concurrency and cancellation
57
58We went through our runs to find where we were burning minutes ([#1362](https://github.com/alchemy-run/alchemy/pull/1362)).
59
60- New pushes in the PR didn't cancel the already ongoing runs, so every commit was still running the full CI even though our agents had already pushed more commits to the branch. Now we cancel the old runs in the same group.
61- Cloudflare tooling tests always ran, regardless of if anything related to it happened in the PR. Now we have scoped it to only run when its packages change.
62- We were accidentally deleting our stable preview workers. Now that's fixed, so wiping our test account doesn't force a full asset upload.
63
64## Rewriting `pkg.alchemy.run`
65
66[`@alchemy.run/pkg`](https://github.com/alchemy-run/alchemy/pull/1516) publishes every PR's packages to an installable URL, like [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new). We built our own version of this as soon as we hit pkg.pr.new's limits, and we host it on Cloudflare with Alchemy. We recently rewrote it so now it supports everything:
67
68- Fork PRs can publish without secrets, because each publish is verified against the GitHub Actions run that built it ([#1681](https://github.com/alchemy-run/alchemy/pull/1681)).
69- Previews can publish only the packages a change affects ([#1727](https://github.com/alchemy-run/alchemy/pull/1727)).
70- Previews stay alive while their PR is open ([#1511](https://github.com/alchemy-run/alchemy/pull/1511)).
71
72## Thanks to Blacksmith
73
74Huge shoutout to our friends at [Blacksmith](https://blacksmith.sh/?ref=alchemy.run) for sponsoring our CI runners. We are utilizing their fast Linux, macOS, and Windows runners. We found Github runners so slow that we were splitting our workloads. With blacksmith's fast runners we just throw our tests across platforms, deploy our content-heavy website, and run checks without having to even think about it.