A Turborepo monorepo, managed with Bun workspaces.
apps/web— the TanStack Start front end, deployed on Cloudflare Workers, with Cloudflare D1 for data. See its README for architecture and setup.
Auth is Clerk: /login signs in, the header shows the
signed-in user. A Clerk organisation gets in when a workspaces row has its id.
bun run add-workspace in apps/web makes the first one of a deployment.
packages/db—@repo/db: the Drizzle schema for D1 and the generated migrations. See its README.packages/services—@repo/services: the service layer. Business rules and validation, on top of@repo/db. The web app calls it through tRPC. See its README.
You need Bun 1.3 or newer and Claude Code. Deploying also needs a free Cloudflare account.
-
Create your repo from the template and clone it. This makes a new private GitHub repo with a fresh history:
gh repo create my-app --template codewithpassion/cf-tanstack-boilerplate --private --clone cd my-app bun installWithout the
ghCLI, press "Use this template" on the template's GitHub page and clone the repo it creates. -
Start Claude Code in that folder and type
/project-init. It asks for a name, renames the project to it and commits. It asks whether you want Clerk auth, then signs you in to Cloudflare, creates the D1 database, applies the migrations and does the first deploy. It asks before creating anything on your account. When it finishes you have ahttps://my-app.<subdomain>.workers.devURL. The app has no database page yet, so there is no in-app check that the database works. TODO(M0): health check -
Commit what
/project-initleft uncommitted and push. That includes the D1database_idinapps/web/wrangler.jsonc. It is not a secret, and without it a fresh clone deploys against the"local"placeholder. -
Optional: run the
setup-previewsskill to get a preview deployment for every pull request. See Pull request previews.
After that, deploying is two commands from apps/web. Run the first one only when packages/db has a new migration:
bunx wrangler d1 migrations apply DB --remote
bun run deployNo Claude Code? bun .claude/skills/rename-project/rename.ts my-app does the rename, and Deploy below has the Cloudflare steps by hand.
bun install
bun run devbun run dev applies the D1 migrations to a local database and starts the web app. The local database is SQLite, run by Miniflare under the Cloudflare Vite plugin, and lives in apps/web/.wrangler. No account and no Docker needed.
Commands at the root run across all apps via Turborepo:
bun run dev— start all apps in dev modebun run build— build all appsbun run deploy— build and deploy all appsbun run preview— preview production buildsbun run check/bun run fix— lint/format the whole repo with Ultracitebun run test— run every workspace's tests
To run a command for a single app, use turbo's filter flag, e.g. bunx turbo run dev --filter=web, or cd apps/web && bun run dev.
The agentum daemon runs agent tasks on a machine you own. It runs on Linux, on x64 or arm64. Install it from your deployment with one command, replacing <your-deployment> with its host name:
curl -fsSL https://<your-deployment>/install.sh | shThe script downloads the binary, checks its SHA-256 hash, and puts it in ~/.local/bin. It also sets up a systemd user service. Pass --no-service to skip that step.
To enrol the machine in the same step, create an enrol code under Settings > Machines and add -s -- --code <code> to the command:
curl -fsSL https://<your-deployment>/install.sh | sh -s -- --code <code>The command runs agentum enroll for you. To update the daemon, run the same command again.
A machine installed before the rename from Harbourmaster keeps working. The daemon reads its old ~/.harbourmaster/config.json and keeps the agents' old work folder. Running the install command again stops and removes the old harbourmaster service and sets up agentum in its place. You can then delete ~/.local/bin/harbourmaster.
The computer client, hm-computer, installs the same way from https://<your-deployment>/install-computer.sh.
Local development needs none of this. You only need the accounts below when you want the app running on the internet.
The web app deploys to Cloudflare Workers. The free plan is enough. Sign in once from the terminal:
cd apps/web
bunx wrangler loginIf you belong to more than one Cloudflare account, wrangler deploy will ask which one to use; set CLOUDFLARE_ACCOUNT_ID in apps/web/.env.local to skip the prompt. The Worker's name comes from apps/web/wrangler.jsonc (the rename-project skill sets it).
The local database only exists on your machine, so a deployed Worker needs a real D1 database. It is on the same free plan as the Worker:
cd apps/web
bunx wrangler d1 create boilerplateThat prints a database_id. Put it in the d1_databases entry in apps/web/wrangler.jsonc, replacing the "local" placeholder, then create the tables:
bunx wrangler d1 migrations apply DB --remoteRepeat the migrations apply --remote whenever packages/db gains a new migration.
bun run deployThat builds and runs wrangler deploy. The URL is printed at the end. If the app has Clerk, run bun run add-workspace --org <Clerk organisation id> --name <name> --remote in apps/web once, because an organisation needs a workspaces row to get in, and without one its users see the NoAccess screen. The app has no database page yet, so there is no in-app check of the database binding. TODO(M0): health check
Every pull request can get its own copy of the app, on a URL like https://pr-42-boilerplate-staging.<subdomain>.workers.dev, with its own empty D1 database that has the PR's migrations applied. Pushing to the PR updates it. Closing or merging the PR deletes the preview and the database. It uses Cloudflare Worker Previews, which is in open beta.
Previews are off until you set them up: run the setup-previews skill (.claude/skills/setup-previews), after the Deploy steps above. Until then the workflow skips.
How it fits together:
env.staginginapps/web/wrangler.jsoncis a second Worker,boilerplate-staging, with its own D1 database. Previews hang off it, never off production.- Its
previewsblock holds the preview's bindings. A preview inherits none from staging, so anything missing there isundefinedat runtime. The D1 entry holds placeholders. Never commit a real id there. .github/workflows/preview.ymlruns on every PR push. It builds withCLOUDFLARE_ENV=staging, thenapps/web/scripts/preview.ts deployfinds or creates the PR's D1 database (boilerplate-preview-pr-<N>), applies the migrations, writes the database id into the built config indist/server/wrangler.jsonand runswrangler preview. The URL goes in a PR comment. On close,preview.ts deleteremoves both.- PRs from forks get no preview, because they cannot read the repo's secrets.
From apps/web, by hand:
bun run staging:deploy # build, migrate and deploy the staging Worker
bun run preview:deploy pr-42 # what the workflow does on push
bun run preview:delete pr-42 # what it does on closeThings to know:
- Preview URLs are public unless you turn on Cloudflare Access for previews.
- Secrets go in the preview base config:
bunx wrangler preview base-config secret put <NAME> --env staging. A preview copies it once, when the preview is created. Existing previews keep the old values until you delete them and re-run the workflow. - Pass
--env stagingto everywrangler previewcommand. Without it,preview deleteandpreview base-configact on the production Worker. - Cron triggers do not fire in previews, and
wrangler taildoes not support them. Use the dashboard's observability logs.