Skip to content

feat(infra): deploy the dashboard to Cloudflare with Alchemy - #116

Open
flamboh wants to merge 4 commits into
stack/sveltekit-3from
stack/alchemy-cloudflare
Open

flamboh wants to merge 4 commits into
stack/sveltekit-3from
stack/alchemy-cloudflare

Conversation

@flamboh

@flamboh flamboh commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Warning

🤖 Claude Opus 5.5 on behalf of Oliver. This changes how production deploys. Production cutover is pending. Do not deploy the prod stage until the cutover below is done.

ELI5

The Cloudflare setup is now a TypeScript program (Alchemy) instead of a wrangler config file. Any copy of the site, such as a throwaway test copy, can be created and deleted with one command.

Flows to exercise

Setup: a Cloudflare API token and account ID exported as CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID, and a small verified SQLite database to seed from.

  1. Personal stage: bun run deploy:cloudflare --stage <you> (e.g. alice-dev). Expect atlantis-<you> and atlantis-db-<you>, all migrations applied, and a printed worker URL. Seed the D1 database, open the URL, and check a dataset page. Then bun run destroy:cloudflare --stage <you> removes both.
  2. Redeploy: an unchanged redeploy is a no-op; a change under apps/web/src rebuilds and uploads.
  3. Stage names: bun run plan:cloudflare --stage alice_dev fails with Invalid stage 'alice_dev'. … for example 'alice-dev'. Stage names are lowercase letters, digits, and single hyphens. Note that Alchemy's default stage (live_$USER) is rejected, so always pass --stage.
  4. Schema drift: bun run --cwd apps/web db:generate on an unchanged schema reports No schema changes, nothing to migrate.

Decisions and edge cases

  • Names: prod keeps atlantis / atlantis-db, and destroying prod retains both. Other stages get a -<stage> suffix. Stage names are validated because the old _→- mapping let alice_dev and alice-dev share resources; with D1 adopt-by-name, destroying one could delete the other's database.
  • Drizzle moves to 1.0.0-rc.5-ab785fc, the build Alchemy pins, because Alchemy rejects the old _journal.json layout. Migration SQL is byte-identical. The converted snapshots stored table-qualified CHECK expressions ("t"."col" > 0) while the new serializer emits unqualified ones ("col" > 0), so db:generate saw every CHECK as changed and emitted a migration rebuilding all six tables that have CHECKs. The snapshots now store the unqualified form; a test runs drizzle-kit generate against a copy of the migrations and fails if it emits anything.
  • State lives in Alchemy's Cloudflare state store so collaborators share it. Any command, plan included, may offer to create or upgrade that store; plan does not change the stage's worker or database.
  • Removed: wrangler.jsonc, the wrangler deploy and d1:* scripts, and adapter-cloudflare (Alchemy injects its own adapter).
  • Temporary patch: @alchemy.run/frontend-frameworks backports its upstream fix for kit 3.0.0-next.27. Drop it on the next beta.
  • Local D1: alchemy dev can't provide cloudflare:workers under Node, so D1 testing means deploying a personal stage. vite dev fails clearly if asked for D1.
  • Preview: bun run --cwd apps/web preview still rebuilds with SQLite and serves it (from feat(web)!: migrate to SvelteKit 3 and Vite 8 with build-time DB driver #115). The Playwright suite runs build:web (D1) and then preview, so every e2e run covers that path on this layer too.
  • Docs pin Wrangler 4.141.0 for the D1 and Time Travel commands.

Production cutover (pending)

Not done in this PR. atlantis-db holds an older schema with an empty d1_migrations table, so a plain deploy would fail with table datasets already exists. Plan:

  1. Record a Time Travel bookmark: bunx wrangler@4.141.0 d1 time-travel info atlantis-db.
  2. Drop every table except _cf_*.
  3. bun run plan:cloudflare --stage prod (database adopted, worker taken over in place, nothing replaced or deleted), then bun run deploy:cloudflare --stage prod. Migrations run on the empty database.
  4. Reload the pipeline data. The reload tooling is still to be decided.

Rollback: bunx wrangler@4.141.0 d1 time-travel restore atlantis-db --bookmark=<bookmark>. The same status and steps are in docs/user/operations.md.

Follow-ups

  • Narrow the Cloudflare API token to the permissions the stack needs after the cutover.
  • Decide the SQLite→D1 reload tooling.

Verification

Automated: bun run format, lint, typecheck, test:web, test:infra (stage validation), test:e2e, landing lint, and build:landing pass; db:generate produces no migration.

Earlier manual checks (before the review fixes): a throwaway stage applied migrations, was seeded from a public sample dataset, and served /, a dataset page, and six API routes byte-identical to local SQLite. An unchanged redeploy was a no-op, destroy removed the stage, and a read-only prod plan showed adopt/in-place takeover with nothing replaced or deleted.

Remaining manual: re-run flow 1 with a hyphenated stage name, and the production cutover itself.


Claude Opus 5.5 · Claude Code (T3 Code)

@flamboh
flamboh added this pull request to stack #118 September 25, 2026 10:46
- Add an infra/ workspace with an Alchemy v2 stack (alchemy and
  @alchemy.run/frontend-frameworks 2.0.0-beta.79, effect 4.0.0-rc.117):
  a D1 database with apps/web/drizzle migrations and a SvelteKit website
  bound to it as DB. Prod keeps the names atlantis-db and atlantis, adopts
  the wrangler-created worker, and retains both on destroy. Other stages
  get suffixed names.
- Add deploy:cloudflare, plan:cloudflare, and destroy:cloudflare root
  scripts, and cover infra in lint, typecheck, and CI.
- Remove wrangler.jsonc, the wrangler deploy and d1 scripts,
  adapter-cloudflare, and wrangler from apps/web. Alchemy injects its own
  adapter at deploy time.
- Upgrade drizzle-kit and drizzle-orm to 1.0.0-rc.5 and convert the
  migrations to the v1 directory layout that Alchemy reads.
- Load cloudflare:workers lazily and keep it external so kit's route
  analysis can run in Node, and reject the d1 driver under vite serve.
- Patch the published SvelteKit adapter to use generateServerInstance,
  backporting the upstream fix for kit 3.0.0-next.27.
- Document Alchemy deploys, migrations, Time Travel, and the first prod
  deploy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant