Log in to Cloudflare
Goribu deploys your app as a Cloudflare Worker (Cloudflare Workers), using Cloudflare's Wrangler tool, which comes installed with Goribu. You need a Cloudflare account, free or paid, to go live. Log in once from the app directory:
pnpm exec goribu cloudflare-login
- What it does: Opens your browser to authorize Goribu's Wrangler, then remembers the login for later deploys.
- CI or a server: Set
CLOUDFLARE_API_TOKENinstead. Create the token from the "Edit Cloudflare Workers" template. A custom domain also needs Zone Read for that domain. - Several accounts: If your login can reach more than one Cloudflare account, set
CLOUDFLARE_ACCOUNT_IDin your shell to choose one. - Only for deploys:
pnpm devandpnpm builddon't need a login. - Other Wrangler commands: Wrangler isn't on your PATH, so run it as
pnpm exec goribu wrangler <args>. It uses the same account as deploy.goribu wrangler deployis refused, because it would skip Goribu's build, resource setup, and migrations.
Check before you deploy
Run these before deploying a change:
pnpm typecheck
pnpm build
pnpm check
pnpm build
pnpm check
pnpm typecheck: TypeScript apps only.pnpm build: Catches build errors and lists which pages are prerendered.pnpm check: Shows what deploy would do, without changing anything: the Worker name, which resources will be created or reused, pending D1 migrations, domain changes, and whether production secrets will be uploaded. It never prints secret values, and doesn't inspect Postgres yet. Bindings from awranglerblock are listed separately, as not inspected or managed by Goribu.
A passing check doesn't prove your login can deploy; an app with no resources may not contact Cloudflare at all. Deploy checks your login itself before it builds.
Deploy
pnpm run deploy
Use pnpm run deploy as a plain pnpm deploy is a different pnpm command. Deploy goes straight to production as there is no staging step. Run the same command for every release.
Each deploy runs these steps in order and stops at the first failure:
- Checks your config and Cloudflare login.
- Builds the app.
- Validates
.env.production, if you have one (production secrets). - Creates or reuses your declared D1 database, KV namespace and R2 buckets, or the Postgres connection.
- Applies pending database migrations.
- Uploads the Worker, static files, and secrets.
- Connects your custom domain, if you set one.
- Your app's address: Wrangler prints it at the end: the
workers.devaddress, plus your domain if configured. - Data is kept: Deploying never deletes resources or their data and local development data is never copied to production.
- After deploying: Open the live app and try the pages and forms you changed, especially anything that touches the database.
Schema changes
Migrations run before the new Worker is uploaded, while the old version is still serving visitors. So each migration must keep working with the old code.
Make breaking changes in steps: add new tables or nullable columns first, deploy code that uses them, then remove the old columns in a later release. Don't ship the addition and the removal in the same deploy.
- A failed migration: Stops the deploy before the new Worker is uploaded. Migrations that already succeeded and resources already created stay in place.
- Migrating ahead of time: Run
pnpm exec goribu migrate --remoteto apply a schema change before deploying. The next deploy finds nothing pending.
See migrations for D1 or Postgres for writing them.
Which requests run the Worker
In production, your Worker only runs for URLs that one of your routes can answer. Everything else, such as prerendered pages, images and bots probing /wp-login.php, is served by Cloudflare's static assets without running your code. This keeps requests fast and Worker usage low without anything to configure.
- Unknown URLs: Get your app's 404 page straight from static assets.
- Dynamic segments: A route such as
/posts/[id]sends everything under/posts/to the Worker, including/posts/42/wp-login.php, which still gets your 404 page. - Catch-all at the root: A route such as
src/routes/[slug].tsxsrc/routes/[slug].jsxmatches every URL, so every unknown URL runs the Worker. The build warns about this and it's recommended to put such routes under a prefix, such as/blog/[slug], when you can. - Rule limit: The build turns your Worker routes into Cloudflare routing rules for you, plus one exception for each static file under a dynamic route, such as
public/posts/feed.xmlunder/posts/[id]. Cloudflare allows 100 rules, so if the build reports too many, group dynamic routes under shared prefixes or move those static files elsewhere. - Development:
pnpm devruns the Worker for every request so data and changes are always fresh.
Live logs
To see what's happening in production, such as errors after a deploy, stream live requests from your Worker:
pnpm exec goribu tail
GET https://app.example.com/hello - 200 OK @ 2026-10-02T10:30:00.000Z
GET https://app.example.com/missing - 404 Not Found @ 2026-10-02T10:30:01.000Z
GET https://app.example.com/boom - 500 Internal Server Error @ 2026-10-02T10:30:02.000Z
- What you see: Each request's status, followed by its
consoleoutput and errors. Failures in background work after a response appear as separate lines, such as[Worker outcome: exception]. - What you don't: Prerendered pages, static files, and unknown URLs never run the Worker, so they don't show up.
- Live only: You see requests from now on, from all visitors, not past logs. Press Ctrl+C to stop.
If deployment fails
Read the first error; it names the step that failed and how to fix it. Then run pnpm run deploy again. A failed deploy always exits with an error and never reports success.
- Login problems: Run
pnpm exec goribu cloudflare-loginagain, or fix your API token, then retry. Deploy never starts a login on its own. - Domain problems: If connecting the domain fails, the new Worker is already live at its other addresses. Fix the reported conflict and deploy again.
- Build output: Production files are written to
.goribu/dist/. Like.wrangler/andgoribu.wrangler.jsonc, it's generated on every build, so don't edit or commit it.