A Cloudflare field guide

How Cloudflare hosts a complete web app.

Six primitives cooperate to host a real application: Workers run your code in every city, KV and D1 keep the data, Cron Triggers do the housekeeping, Secret Store guards the credentials, and Turnstile waves humans through. This guide explains each piece — then follows one signup all the way through.

300+cities keeping a copy of your app warm
≈0 mscold-start penalty, thanks to V8 isolates
$0egress fees between Cloudflare and visitors
1deploy unit: one script plus static assets

Part 01 · The map

One Worker in the middle, services a binding away

There is no app server to babysit. A visitor's request is answered by the nearest Cloudflare colo — one of 300+ cities — where a tiny isolate boots your Worker script. Data, secrets, schedules and verification all hang off that script as bindings.

Primitive 01 · Compute

Workers — one script answering every city

A Worker is your whole backend folded into one entry point: an event handler that runs on Cloudflare’s network, in the city nearest to each visitor. You don’t get a server — you get events, answered at the edge.

How it works

  • V8 isolates, not containers. Each request is served by a lightweight isolate — the same engine class as a browser tab — that boots in roughly 5 ms, so cold starts are effectively invisible.
  • One copy per city. Deploy once and your script is distributed to 300+ locations; the closest one picks up every request.
  • Static assets are first-class. Point the assets directive at a folder and your HTML, CSS and JS are served straight from the edge — requests for files never even reach your script.
  • Stateless on purpose. An isolate can be evicted at any moment, so surviving across requests via globals is a bug in waiting. Persistent state belongs in KV, D1 or Durable Objects.
  • Event-driven. fetch answers HTTP, scheduled answers the clock, and WebSockets, email and queues bring their own events.

Reach for it when

  • You need routing, auth or session checks before anything else runs.
  • You render HTML responses on the fly.
  • You expose API endpoints or accept webhooks.
  • You want cache-boosting logic — headers, purges, A/B splits — right where requests arrive.

Think twice when

  • Work is long-running or CPU-heavy — execution is metered per request; offload to Queues or cron.
  • You’d hold state in memory — use D1, KV or Durable Objects instead.
  • You depend on every Node.js API — nodejs_compat covers much of it, not all of it.

Primitive 02 · Storage

KV — a Map() that spans the planet

Workers KV is a global dictionary. Write a value on one side of the planet and it replicates outward within about a minute — then every colo reads it locally, forever. It trades instant consistency for unbeatable read reach.

How it works

  • Writes fan out. A put() succeeds immediately, then the value spreads to the edge — up to 60 seconds to reach all locations.
  • Reads stay local. Once a key has been read nearby, later reads are answered from a local copy — no cross-ocean trip.
  • Keys expire themselves. Set an expirationTtl and the platform garbage-collects on your behalf.
  • Built for read-heavy workloads. Writes are comparatively dear; reads are cheap, fast and plentiful.
  • Namespaces, not tables. Create a namespace, bind it, and list keys by prefix when you need inventory.

Reach for it when

  • You need session tokens readable (and revocable) at every edge.
  • You serve feature flags and hot configuration.
  • You cache pre-rendered HTML fragments or API responses.
  • Every request greets the same small, slow-changing data at startup.

Think twice when

  • Reads must see their own writes — that’s D1 territory.
  • Workloads are write-heavy: counters, votes, locks — use D1 or Durable Objects.
  • Values are big or immutable files — R2 is shaped for blobs (KV caps values at 25 MiB).

Primitive 03 · Database

D1 — SQLite with a service desk

D1 is SQLite as a platform: real SQL — joins, indexes, transactions — without a server, a connection string or a capacity plan. Your Worker talks to it through a binding, and the data never leaves the network.

How it works

  • Actual SQL. Tables, joins, foreign keys, WAL — the full SQLite toolbox, with parameterized ?1 placeholders built in.
  • Bound, not dialed. No URLs, passwords or pooling code. env.DB is a typed handle wired at deploy time.
  • Built-in resilience. Time Travel can restore the whole database to any minute of the last 30 days — ideal after a regrettable UPDATE.
  • Read replication (beta). Read-only copies spread toward the edge for low-latency reads closer to users.
  • Sized to breathe. Up to 10 GB per database; reach for R2 when your data is blob-shaped.

Reach for it when

  • You keep users, accounts and profiles — the system of record.
  • Data is relational: orders, invoices, joins and aggregates.
  • Writes must read their own result, atomically.
  • Schema matters — migrations are versioned in your repo.

Think twice when

  • Values are large files or media — R2 wants them.
  • One hot key needs constant coordination — Durable Objects.
  • A dataset grows past the per-database limit — shard it up front.

Primitive 04 · Operations

Cron Triggers — your code, on the clock

Cron Triggers are the app’s heartbeat. Schedule expressions in the deploy config wake a scheduled() handler in the same Worker — no request, no visitor, nothing to keep alive.

How it works

  • Declared, not installed. Cron expressions live in wrangler.jsonc; Cloudflare fires your handler on schedule, evaluated in UTC.
  • Same Worker, full powers. scheduled() carries every binding — it can read D1, refresh KV and reach out to APIs exactly like a fetch could.
  • Several schedules, one script. Each expression is its own trigger; branch inside on controller.cron to route each to the right job.
  • Background finish. ctx.waitUntil() extends the runtime so follow-up work survives past the return.

Reach for it when

  • You export nightly D1 backups or run exports.
  • Stale sessions and expired tokens need evicting.
  • KV caches (flags, config) should be warmed before morning traffic.
  • Digest emails and reports go out on a schedule.

Think twice when

  • A job must run exactly once — design handlers to be idempotent anyway.
  • Work exceeds per-request CPU limits — chunk it, or use Queues.
  • The trigger is user action — that’s a plain request, not a cron.

Primitive 05 · Security

Secret Store — credentials without the paper trail

Some values must exist but must never be seen. Secrets are encrypted at rest, versioned, and injected into your Worker only at runtime — never in code, never committed, never in cleartext config.

How it works

  • Config vs credentials. Plain settings live in vars and are visible in your config; secrets are encrypted and decrypted only when a Worker runs.
  • One command to set. wrangler secret put uploads a value straight from your terminal — no file ever touched disk.
  • The vault for scale. Secret Store centralizes the rest: group secrets into packages, bind one package to many Workers, and rotate a credential once — every consumer picks up the new version.
  • Built for tenants. Per-customer packages make “bring your own API key” products practical without custom plumbing.

Reach for it when

  • Third parties ask you to keep a key: billing, email, AI providers.
  • OAuth client secrets must live somewhere safer than Git.
  • Services talk over signed or mTLS credentials.
  • Each tenant carries their own credentials.

Think twice when

  • The value should ship to the browser — nothing client-visible is secret.
  • You log generously — print outcomes, not values, or errors will leak them.
  • Keys never rotate — put rotation on the calendar before you need it.

Primitive 06 · Trust & safety

Turnstile — proof-of-human, puzzles optional

Turnstile decides who gets in without asking visitors to click traffic lights. It reads signals in the background and issues a cryptographic token your Worker can verify server-side before committing anything.

How it works

  • Three flavors. Managed (rare checkbox), Non-Interactive and Invisible — pick per form, dial friction up or down.
  • Privacy by design. No cookies, no puzzles, no harvesting personal data to sell against the visitor.
  • Two halves, both yours. A widget in the browser mints a token; your Worker checks it against siteverify — the server check is the only one you can trust.
  • Single-use proof. Tokens expire and don’t replay — a reused token simply fails.
  • Free, unlimited verifications. Wall off every sensitive route without counting pennies.

Reach for it when

  • Signups, logins and password resets invite bots.
  • Comments, reviews and spam-prone forms stay open.
  • API endpoints must never be scripted at scale.

Think twice when

  • Visitors block scripts — keep a fallback plan for edge cases.
  • Fraud pressure is deep — pair it with rate limiting and WAF rules.
  • You skip siteverify — a client-only badge proves nothing.

Putting it together

One signup, end to end

Follow a single request through the stack — every primitive gets exactly one job, and the pieces hand off cleanly.

  1. 1

    First contact — assets at the edge

    The browser asks for /. The nearest colo serves the HTML shell and hashed assets from cache — matched files don’t even wake your script.

  2. 2

    A human check, quietly

    The Turnstile widget verifies the visitor in the background and writes a one-time token into the form. Most people never notice it happened.

  3. 3

    Form posted, badge checked

    POST /api/signup lands in the Worker. Before any business logic, it posts the token to siteverify — a failed check 403s a bot without touching the database.

  4. 4

    Credentials surface — already injected

    The billing API key arrives in env from Secret Store. No fetch to a vault, no config values: rotation happened upstream and every bound Worker just sees the new version.

  5. 5

    The write that counts

    An INSERT — parameterized — lands in D1. The user row and a starter profile go in together, and the account now reads back true.

  6. 6

    Session issued at the edge

    A session token goes into KV with a TTL. From now on, every auth check is a local get() in whichever colo the user happens to be near.

  7. 7

    While you sleep, the clock wakes the Worker

    At 02:17 UTC, the cron fires scheduled(): expired sessions are evicted, yesterday’s rows are backed up, and tomorrow’s flags are warmed.

Decision guide

KV or D1? Let the write pattern decide

Both store data; they optimize for opposite traffic. Ask one question: does a read need to believe the last write? If yes — D1. If reads can lag a minute — KV.

Comparison between Workers KV and D1
Aspect Workers KV D1
Optimized for High-volume reads, anywhere on Earth Structured writes and queries — the source of truth
Data model Key → value; keys ≤ 512 B, values ≤ 25 MiB SQL tables with joins, indexes and transactions
Consistency Eventual — reads can trail writes up to 60 s Strong — committed writes read back immediately
Sweet spot Sessions, flags, warm configuration, pre-rendered fragments Users, orders, invoices — anything with relations
Watch out for Hot writes and locks — take those to D1 or Durable Objects Blobs go to R2; past 10 GB per database, shard

And the rest of the family, same binding story: R2 for objects and large files, Queues for async work, Durable Objects for truly stateful coordination.

Ship it

From zero to the edge in four steps

The whole stack deploys the same way: the script, the bindings and the schedule travel together in one config file.

  1. 1

    Scaffold

    One command writes a starter Worker, a config file and an assets directory — with local dev running on the open-source runtime.

    terminal
    $ npm create cloudflare@latest my-app
    $ cd my-app && npx wrangler dev
  2. 2

    Declare the pieces

    Every primitive’s binding — plus the housework schedule — lives beside your code. The platform wires them at deploy time.

    wrangler.jsonc
    {
      "name": "my-app",
      "main": "src/index.ts",
      "assets": { "directory": "./public" },
      "kv_namespaces": [
        { "binding": "KV", "id": "<id>" }
      ],
      "d1_databases": [
        { "binding": "DB",
          "database_name": "app-db",
          "database_id": "<id>" }
      ],
      "triggers": { "crons": [ "17 2 * * *" ] }
    }
  3. 3

    Hand over the keys

    Migrate the schema forward, then upload secrets straight from the terminal — or group and bind them once as packages in Secret Store.

    terminal
    $ npx wrangler d1 migrations apply app-db
    $ npx wrangler secret put STRIPE_SECRET_KEY
  4. 4

    Ship — the network is the infrastructure

    Deploy pushes your script to 300+ locations in seconds. Tail logs live, watch requests arrive from around the globe, and roll back any deploy in one click.

    terminal
    $ npx wrangler deploy
    Deployed my-app ...
      https://my-app.<your-subdomain>.workers.dev

Notice what’s missing: nothing to provision, no capacity math, no patching windows — every deploy is atomic, and rollbacks are always one step behind you.