Skip to content

Commit 02def5c

Browse files
Brooooooklynclaude
andauthored
feat(deploy): direct-to-Cloudflare deploy via --backend cloudflare / --provision (#273)
Adds `void deploy --backend cloudflare` (+ `--provision`) to deploy a Void app directly to the user's own Cloudflare account, bypassing the Void platform. Includes the orchestrator, the shell-to-wrangler provisioner, a comment-preserving wrangler.jsonc writer, an isolated env-schema probe, a self-host secrets gate, and docs. Security: the credentialed `wrangler deploy` runs after an untrusted `vite build` (Cloudflare creds scrubbed), so `verifyGeneratedDeployConfig` treats the emitted dist/ssr/wrangler.json as adversarial and rejects any capability the pre-build root config did not grant — bindings, unsafe, routes, ai, durable objects, migrations, container applications, cron triggers, and queues are all pinned to a pre-build source of truth, plus an account pin, a wrangler-uploader content-hash re-checked before every credentialed spawn, a secrets gate reconciling account + worker name, and a no-provision config-laundering guard. The `--provision` config-laundering delta-authorization, the verify→deploy TOCTOU, and full build isolation are tracked as follow-ups in #283. The managed `void deploy` path forks before any of this and is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wnwcxjavdb2xys8XGZora
1 parent 2e443b2 commit 02def5c

30 files changed

Lines changed: 31888 additions & 93 deletions

‎docs/guide/auth.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,14 @@ Void manages Better Auth migrations as part of the normal Void migration flow:
249249
- deploy runs auth migrations together with app migrations
250250
- users do not run a separate Better Auth CLI path
251251

252+
This applies to `void deploy` (the managed platform), which creates the Better Auth
253+
tables at runtime after dispatch. Deploying to your own Cloudflare account with
254+
[`--backend cloudflare`](/integrations/cloudflare) does **not** run that step: there,
255+
your checked-in `db/migrations/*.sql` must already produce the Better Auth schema, and
256+
deploy fails closed if they do not. Define the tables in `db/schema.ts` and run
257+
`void db generate` — Drizzle's `.unique()` and `.references()` are opt-in, and the
258+
constraints are verified too. See [#274](https://github.com/voidzero-dev/void/issues/274).
259+
252260
## Unsupported Modes
253261

254262
Void-managed Better Auth is supported only for Cloudflare Void apps in v1.

‎docs/guide/deployment.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,6 @@ Connect the repository to your project once with `void github connect <project>
106106

107107
## Other Targets
108108

109-
If you prefer to deploy directly to your own Cloudflare account instead of using Void's managed platform, see the [Cloudflare integration guide](../integrations/cloudflare.md).
109+
If you prefer to deploy directly to your own Cloudflare account instead of using Void's managed platform, run `void deploy --backend cloudflare` (add `--provision` on the first deploy to create the D1/KV/R2/Queues/Hyperdrive resources your app needs). This uses your local wrangler auth and root `wrangler.jsonc` -- pin an account (`account_id` or `CLOUDFLARE_ACCOUNT_ID`) and keep real secrets in `wrangler secret put` rather than `.env`. `wrangler login` is enough to authenticate, with one exception: provisioning a **Hyperdrive** config for the first time needs `CLOUDFLARE_API_TOKEN`, because wrangler exposes no machine-readable Hyperdrive list for Void to check against (an already-provisioned Hyperdrive app deploys fine under `wrangler login`). In v1 it deploys **full Void apps on the Cloudflare Workers target** (D1/KV/R2/Queues/Hyperdrive; auth and ISR on D1/SQLite); framework SSR (TanStack Start, React Router, vinext, SvelteKit, Nuxt, Analog, Astro), static/SPA/SSG apps (including `output: "static"`), WebSocket/Durable Object apps (`*.ws.ts`), a custom `migrations_pattern`, `--skip-build`, `node`/`bun`/`deno` targets, and PostgreSQL apps with auth all fail closed with guidance. Auth apps must ship checked-in migrations that produce the Better Auth schema. Provision is a single-operator, dev-machine step and is disabled in CI. See the [Cloudflare integration guide](../integrations/cloudflare.md#deploy-to-your-own-cloudflare-account) for the full walk-through and scope.
110110

111111
To deploy to Node.js, Bun, or Deno instead of Cloudflare, set [`target`](../reference/config.md) in `void.json`. This builds a standalone server you can run anywhere, including Docker, Railway, and Fly.io. These targets do not have access to Void platform features such as D1, KV, R2, built-in auth, Workers AI, or cron scheduling. See the [Node.js, Bun, and Deno guide](../integrations/nodejs-bun-deno.md).

‎docs/guide/env-vars.md‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,23 @@ In practice `env.ts` should only import schema helpers from `void/env` and — a
109109

110110
Void uses Vite's standard `.env` convention to populate the schema:
111111

112-
| File | Loaded in dev | Shipped on deploy |
113-
| ----------------------- | ------------- | ------------------ |
114-
| `.env` | yes | yes (`plain_text`) |
115-
| `.env.local` | yes | no |
116-
| `.env.production` | yes | yes (`plain_text`) |
117-
| `.env.production.local` | yes | no |
112+
Whether a file's values ship depends on which deploy path you use:
113+
114+
| File | Loaded in dev | Shipped by `void deploy` | Shipped by `--backend cloudflare` |
115+
| ----------------------- | ------------- | ------------------------ | --------------------------------- |
116+
| `.env` | yes | yes (`plain_text`) | yes (worker `vars`) |
117+
| `.env.local` | yes | no | **yes** (worker `vars`) |
118+
| `.env.production` | yes | yes (`plain_text`) | yes (worker `vars`) |
119+
| `.env.production.local` | yes | no | **yes** (worker `vars`) |
118120

119121
`.local` files are gitignored by convention — use them for secrets you don't want in source control.
120122

123+
::: warning `.local` files are not deploy-excluded on `--backend cloudflare`
124+
Managed `void deploy` reads only `.env` and `.env.production`, so a `.local` file keeps secrets off the platform. The self-host `void deploy --backend cloudflare` path instead runs Vite's production env loading, which reads all four files and bakes them into the worker's `vars` as plaintext. A value that is also `export`ed in the shell with the same value is stripped back out, so this bites hardest in CI, where the variable usually only exists in the file.
125+
126+
On that path, keep real secrets out of every `.env*` file and use `wrangler secret put <NAME>` instead. See [Cloudflare deploy](/integrations/cloudflare).
127+
:::
128+
121129
### Dotenv variable expansion
122130

123131
Values can reference other keys defined in the same (or earlier-precedence) `.env` file using `${VAR}` or `$VAR`:

‎docs/guide/queues.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Void supports Cloudflare Queues for asynchronous message processing from a top-l
1010

1111
Create files in `queues/**/*.ts`; `.mts`, `.js`, and `.mjs` also work. The queue name is inferred from the filename. For example, `queues/emails.ts` creates a queue named `"emails"`, and `queues/order/notifications.ts` creates `"order/notifications"`.
1212

13+
::: warning Nested files produce a name that cannot be deployed
14+
Cloudflare queue names allow only letters, digits and `-`, so a name containing `/` is rejected when the queue is provisioned — on both the managed platform and a self-hosted `--backend cloudflare` deploy. Nested files work in local development, but keep queue files flat (`queues/order-notifications.ts` → `"order-notifications"`) for any app you intend to deploy.
15+
:::
16+
1317
Each queue file should export a default handler wrapped with [`defineQueue`](../reference/api.md#definequeuet-handler). The generic `<T>` parameter defines the message body type. That is the type of each `msg.body` in the batch, and it is also used by the typed `queues` proxy for `send()` calls.
1418

1519
```ts

0 commit comments

Comments
 (0)