# Cloudflare Workers

> The same core on Workers and D1 — one binding, the schema applied by wrangler, and a Cron Trigger if you want the index drained.

Section: Deploy · Source: https://titen.dev/docs/deploy-cloudflare
Derived from: https://github.com/RamaAditya49/titen/blob/main/docs/deployment/cloudflare.md

---
The Worker owns bindings and nothing else. Routing, authorization, ranking and lifecycle
live in the shared core, so the same 60-test contract suite runs against Bun with SQLite and
against Workers with D1, and neither runtime can quietly drift from the other.

## Five commands

```sh
wrangler d1 create titen
# copy the printed database_id into wrangler.jsonc

pnpm titen schema | wrangler d1 execute titen --file=-
pnpm titen bootstrap --org 'My Org' --print-sql | wrangler d1 execute titen --file=-

pnpm deploy:worker
curl https://titen.<your-subdomain>.workers.dev/healthz
```

`titen schema` prints every migration statement plus the `titen_migrations` bookkeeping
insert, so the schema reaches D1 through `wrangler d1 execute` rather than through
application code. `bootstrap --print-sql` does the same for the first organization: the
`INSERT` statements go to stdout, and the raw key goes to **stderr** so a pipe cannot
capture it into a file.

<div class="callout callout--alarm">
<strong>The key is on stderr, once</strong>

`--print-sql` puts only the key *hash* into the SQL. The raw `titen_sk_…` value is printed
to your terminal and never recoverable afterwards. Store it before you run anything else.
</div>

## The binding is the configuration

<figure class="code"><figcaption>wrangler.jsonc<b>jsonc</b></figcaption>

```jsonc
{
  "name": "titen",
  "main": "src/runtime/cloudflare/worker.ts",
  "compatibility_date": "2026-07-01",
  "workers_dev": true,
  "observability": { "enabled": true },
  "vars": {
    "TITEN_REVISION": "dev",
    "TITEN_AUTO_MIGRATE": "0"
  },
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "titen",
      "database_id": "replace-with-your-d1-database-id"
    }
  ]
}
```

</figure>

`TITEN_REVISION` is a non-secret build marker echoed by `/healthz` and `/readyz`.
`TITEN_AUTO_MIGRATE=1` lets the Worker apply pending migrations on cold start instead: one
attempt per isolate, and an isolate that loses the race swallows the error and continues,
because readiness reports the real applied version either way.

Leaving it at `0` is the safer default: the migration then lands when you run it, instead of
racing several isolates during a traffic spike. If the schema is missing, `/readyz` answers
`503 NOT_READY` with `checks.migrations: "pending (applied 0, expected 9)"` rather than
serving half a service.

## Background repair is external here

A Worker has no in-process timer, so the Bun runtime's maintenance loop becomes a
`scheduled` handler driven by a Cron Trigger. The pass runs inside `waitUntil` so the
platform does not cancel it when the handler returns.

<figure class="code"><figcaption>wrangler.jsonc<b>jsonc</b></figcaption>

```jsonc
"triggers": { "crons": ["*/1 * * * *"] }
```

</figure>

The Worker reports `background_repair: "external"` unconditionally, because it cannot
observe whether a Cron Trigger is configured. Without one, drive `POST /v1/index/drain` and
`POST /v1/webhooks/deliver` yourself.

## What you do not need

The memory loop and every collaboration route run on the D1 binding alone.

<div class="table-wrap">

| Not required | Why |
| --- | --- |
| `nodejs_compat` | The core is plain `fetch`, `URL` and Web Crypto |
| Vectorize, Workers AI | Retrieval is lexical FTS5 on this runtime today |
| Cron Trigger | Only indexing and webhook delivery need one |
| KV, R2, Queues, Durable Objects | D1 is the only store; the outbox is a table |

</div>

## Planned, not yet active

<div class="rules">

<p><strong>Vectorize plus Workers AI.</strong> A Vectorize store and a Workers AI embedder exist in <a href="https://github.com/RamaAditya49/titen/blob/main/src/runtime/cloudflare/vectors.ts">src/runtime/cloudflare/vectors.ts</a>, and the capability is built only when both the <code>VECTORIZE</code> and <code>AI</code> bindings are present. Neither is declared in <code>wrangler.jsonc</code>, so it returns nothing and readiness reports <code>vector: "disabled"</code>. Semantic retrieval is live on Bun and SQLite; see <a href="/docs/vectors">Semantic vectors</a>.</p>
<p><strong>Channel serving</strong> through a scoped service credential for a CRM or chatbot gateway.</p>
<p><strong>Memory Atlas</strong> as a read-only browser client against authenticated REST.</p>

</div>

## Secrets and verification

Store bootstrap and model secrets with `wrangler secret put`, never in `wrangler.jsonc`, and
issue a distinct revocable key per agent or service. The release gate for this runtime: an
unauthenticated protected request returns a JSON `401`, cross-tenant access returns a
non-disclosing not-found, observation and context compile work with no Vectorize binding, a
cache-busted production response reports the deployed revision, and the rollback artifact plus
migration compatibility are known before release.

<div class="callout">
<strong>Current platform limits are research, not measurement</strong>

Cloudflare's own limits and pricing in the repo's
[blueprint](https://github.com/RamaAditya49/titen/blob/main/blueprint.md) must be refreshed
before you rely on them for production sizing. The loop latency figure in the deployment doc
was taken against local D1, not production.
</div>

## Next

<div class="cards">

<a href="/docs/deploy-vps"><strong>Bun + SQLite (VPS)</strong><span>The runtime verified live, with systemd, hardening and measured footprint.</span></a>
<a href="/docs/backup-export"><strong>Backup &amp; export</strong><span>Move canonical records between a Worker and a VPS without losing evidence links.</span></a>
<a href="/docs/api"><strong>HTTP API</strong><span>All 58 routes with their scopes, envelopes and error codes.</span></a>

</div>