Building From Source
Two commands turn a clean clone into a deployable tree, and they are not interchangeable.
| route | command | needs | takes |
|---|---|---|---|
| payload | bun run hydrate |
network | seconds |
| source | bun run build:local |
network, PHP, composer, node 24+ | minutes |
bun run hydrate takes the payload route by default and falls back to the source route when no
payload exists. --payload-only forbids the fallback; --from-source skips straight to it.
Pages
- The Steps: the seventeen build steps, from the interpreter to the assets.
- Build Order and Caching: why the order is fixed, what is cached, and partial rebuilds.
- Upgrading Drupal Core: the sequence for a core bump and what a patch release moves.
Why Two Routes
assets/ is generated packs and .interp/ holds the interpreter. Both are gitignored, so a clean
checkout has neither and wrangler deploy has nothing to upload.
The payload route downloads them as one verified tarball. It is a plain HTTPS GET: no Docker, no
gh auth, no PHP, no Cloudflare credential. That is what makes the Deploy to Cloudflare button
work. Its build command is bun run hydrate (build.command in wrangler.jsonc), and Workers
Builds has no Docker to build with.
The source route regenerates the same bytes locally. It covers what the payload cannot: a checkout of
a commit no release was cut from, and a repository with no release yet. It produces every artifact
the payload carries, assets/prefill.json included.
Where a Payload Comes From
hydrate tries four places and takes the first that answers, and every one of them is verified
against the SHA256SUMS published beside it.
| order | source | what it is |
|---|---|---|
| 1 | --from=<path> or dist/ |
a tarball on disk, which outranks the network |
| 2 | payloads/v<version>/ on R2 |
the release, mirrored to the CDN |
| 3 | the GitHub Release | the same bytes, canonical |
| 4 | payloads/dev-<branch>/ on R2 |
that branch's tip, replaced on every push |
The CDN copy is tried before the GitHub Release. A Workers Builds job once stalled for its whole timeout on a release asset download, while the same 13.4 MB restored from the CDN in seconds. The Release stays canonical and stays the fallback, so a CDN outage costs a retry rather than a deploy.
The dev line is tried last and never displaces a release. It is keyed by branch: the checkout's
own branch first, then master. It lets a checkout on a commit no release was cut from download a
tree in seconds instead of building one with a toolchain the deploy container does not have.
bun run build:local is still there when you want the bytes regenerated rather than fetched.
--dev[=branch] takes the dev line directly. bun run hydrate -- --dev=master skips dist/
and every release and downloads the tip of that branch, and a bare --dev names the checkout's own
branch. It is for a checkout whose package.json already carries the next version: without it, a
release of the previous version answers first and lands older bytes under newer code. It refuses to
combine with --from, --tag or --from-source, since each of those names a different payload. The
bytes are a branch tip, so the next push replaces them and the run says so.
Both lines are published by CI from the tarball the release job already built, so nothing is
rebuilt to publish it. The keys come from scripts/payload-cdn.ts, which the resolver and the
workflows both read; tests/node/workflows.spec.ts fails if a workflow starts spelling a key of its
own.
Quick Start
git clone https://github.com/drupflare/worker.git
cd worker
bun install # postinstall restores the interpreter from the CDN, verified by sha256
bun run hydrate # payload if there is one, source build if there is not
bun run devTo see what a build would do without doing it:
bun run build:planPrerequisites, Per Route
The payload route needs Bun and a network. Nothing else.
The source route needs each of these, and bun run build:plan names any that are missing before it
spends a minute:
| tool | version | used by | note |
|---|---|---|---|
bun |
latest | everything | |
node |
24+ | core, sql, patch |
node:sqlite, which Bun does not ship |
php |
8.3+ | site, twig |
with pdo_sqlite, sqlite3, mbstring, iconv |
composer |
2 | tree |
the Drupal release tarball is core only |
git |
any | siblings |
|
tar |
any | tree |
Two of these run under node rather than bun. pack-sql.ts needs node:sqlite.
pack-drupal.ts writes core.bin.gz from gzipSync(blob, { level: 9 }), and the two runtimes do
not emit the same deflate stream for the same input, so running it under Bun would move a shipped
asset with nothing reporting it.
Related
- Configuration: every variable and binding, and what breaks when one is wrong
- Repository Layout: every path outside
src/and how it arrives - Scripts: why each script is in the language it is in