# Cloudflare is more than DNS: what you can build on it

Cloudflare can host full-stack apps, databases, file storage, scheduled jobs and AI, not just DNS. What each piece does, what it costs, where it hurts, and what I run on it.

- Author: Harish Chouhan
- Published: 2026-10-23
- Page: https://workerdock.app/blog/cloudflare-is-more-than-dns/

My poll on X got a mixed pile of answers: DNS only, R2 for backups, a few other bits. Hardly anyone knew Cloudflare can host a full-stack app, with the code, the database and the file storage all in one place. It can, and a small one fits on the free plan or the $5-a-month paid minimum.

![Diagram of Cloudflare in three layers: network (DNS, CDN, SSL, WAF, Turnstile, Tunnel), compute (Workers, static assets, cron, queues, Durable Objects, Workflows, Containers) and data (D1, KV, R2, Durable Objects, Hyperdrive, Vectorize)](/assets/img/blog/cloudflare-is-more-than-dns/three-layers.webp)
*Cloudflare in three layers. Most people only use the first one.*

## Three layers

Cloudflare's products fall into three layers. The **network** layer is DNS, caching, SSL and security. Traffic flows through it on the way to your site. Almost everyone who uses Cloudflare is here. **Compute** is where your code runs: Workers first, then cron jobs, queues and a few others. **Data** is where your stuff lives, and D1, KV and R2 cover most of it.

The first layer works with any host. You can keep your site on a cheap server and still use Cloudflare for DNS and protection. Layers two and three are what let you drop the server altogether.

## The network layer, quickly

If your domain is on Cloudflare, you already have a lot of this switched on or one click away.

Free HTTPS certificates renew themselves. The CDN keeps copies of your images, CSS and pages in cities around the world, so a visitor in Sydney doesn't wait for a server in Frankfurt. A basic firewall and DDoS protection come with the free plan, and you can add rules on top, like blocking a country or throttling your login page.

Two smaller tools are worth knowing. **Turnstile** is a free CAPTCHA replacement: drop a widget in a form, check the result on your server, done. **Tunnel** lets you expose a service running on your own machine without opening any ports, and **Access** puts a login in front of anything, so only your team gets in.

None of that needs a single line of Worker code. If this is as far as you go, that's a perfectly good place to stop.

## Workers

Most of the rest rests on this one. A Worker is a small program that runs when a request comes in. You write it in JavaScript or TypeScript (Python and WebAssembly work too), Cloudflare runs it in a data centre near the visitor, and there's no server for you to patch.

```ts
export default {
  async fetch(request: Request): Promise<Response> {
    return new Response("Hello from Cloudflare");
  },
};
```

That's a whole app. `npx wrangler deploy` and it's live.

Pricing is the unusual part. You're billed on CPU time, the milliseconds your code is actually computing. If it spends 300 ms waiting for a database or another API, you don't pay for the wait. The free plan gives you 100,000 requests a day with 10 ms of CPU per request. The paid plan is $5 a month and includes 10 million requests and 30 million CPU milliseconds. Past that it's $0.30 per extra million requests and $0.02 per extra million CPU milliseconds.

Ten milliseconds sounds tiny, and for a heavy job it is. For normal web work it's plenty. One of my busiest Workers averages under 3 ms of CPU per request (about 2.9 million CPU milliseconds over roughly a million requests this month). Resizing a big photo inside a Worker is a different story, and you'd want to do that somewhere else.

## The supporting cast

A few more compute pieces turn up as soon as an app grows. I'll keep each one short.

**Static assets.** A Worker can serve your HTML, CSS and images next to its code, and requests for those files are free and unlimited, as long as no Worker code has to run for them. A request that does invoke your Worker (server-side rendering, or routes you force through it) is billed like any other Worker request. There's an older product, Pages, that does a similar job for static sites and frameworks. My [free hosting guide](/blog/launch-web-app-free-on-cloudflare/) covers Pages and its limits.

**Cron Triggers** run a Worker on a schedule: clean up old rows at 3 am, pull a report, ping an API. In your config it's one line, `"triggers": { "crons": ["0 3 * * *"] }`, and your Worker gets a `scheduled()` handler. Testing locally trips people up, because `wrangler dev` won't fire schedules by itself. While `wrangler dev` is running, send a request to `/cdn-cgi/local/scheduled` and it runs once. That endpoint only exists locally.

**Queues** hand slow work to a second Worker. The web request drops a message in and answers right away. Meanwhile the consumer sends the email or calls the sluggish API. Failed messages get retried, and the ones that keep failing can go to a dead-letter queue so they don't block everything behind them. You get a million operations a month on the paid plan, then $0.40 per million. A normal message takes three operations (one write, one read, one delete), so a million operations is roughly 330,000 messages.

**Durable Objects** are the odd one out. Each one is a tiny piece of code with its own storage, and only one instance of a given object runs at a time. That sounds like a limitation until you need a chat room, a rate limiter or a live counter, where you want one source of truth. They hold websocket connections too, so many browsers can watch the same thing change. [Webhookflare](/directory/webhookflare/) is a neat small example: every webhook URL is its own Durable Object, and requests pop up in your browser as they land. The SQLite flavour is on the free plan.

**Workflows** run long, multi-step jobs that survive failures. Send an email, wait two days, check what the user did, send another. You write it like normal code and Cloudflare remembers where it was.

Two more exist for work that doesn't fit in a Worker. **Containers** run a regular container image next to your Worker (paid plan only), and **Browser Rendering** gives you a headless browser for screenshots, PDFs and scraping.

## Where the data goes

| If your data is... | Reach for |
| --- | --- |
| Rows you filter and join | D1 |
| Small values, read often, rarely changed | KV |
| Files of any size | R2 |
| Live state for one room or one user | Durable Objects |
| Already in Postgres or MySQL elsewhere | Hyperdrive |
| Embeddings for AI search | Vectorize |

### D1, and the benchmark I'd rather not have run

D1 is SQLite, hosted. You write plain SQL. The paid plan includes 25 billion rows read, 50 million rows written and 5 GB of storage every month. D1's free plan gives you 5 million row reads and 100,000 row writes a day.

I was weighing up moving a notification app of mine, Trigv, from a normal server to Workers and D1. It takes lots of small writes, so I wrote a benchmark to see how D1 copes: 250,000 inserts into one database, as fast as it would go.

My target was 833 writes a second. It managed about 370. Roughly 0.7% of writes failed with network errors, and the median write took over a second. Not great, until you remember I was shoving a quarter of a million writes at it in minutes. That isn't a small product's traffic. Still, it matched what Cloudflare's docs say plainly: a D1 database handles one query at a time. At 1 ms per query that's about 1,000 a second, at 100 ms about 10, and writes take longer because they're saved in several places.

A single database tops out at 10 GB on the paid plan, and Cloudflare says that limit can't be raised. The free plan caps it at 500 MB. But you can have a lot of databases, up to 50,000 on paid, so one per customer is a real pattern. If you're sending many statements, `batch()` them in one trip. And if the writes matter, retry on failure and make each write safe to repeat, or you'll end up with duplicates.

### KV

KV stores a value under a key, and reads are fast almost everywhere. Changes take a little while to spread. Settings, feature flags, cached API responses and sessions fit well.

The catch is writes. The free plan allows 1,000 a day, and a busy app burns through that before lunch. Anything that changes often belongs in D1.

### R2

R2 is file storage that speaks the S3 protocol, so most S3 tools work unchanged. The money part is what Cloudflare doesn't charge for: data going out, ever. Storage is $0.015 per GB a month with 10 GB free. Writes and listings cost $4.50 per million operations and reads $0.36 per million, with a million and ten million free each month.

Do the sum on a backup: 100 GB is 90 GB over the free allowance, so about $1.35 a month. That's why R2 is where I keep my own server backups.

### The rest

**Hyperdrive** speeds up a Worker's connection to a Postgres or MySQL database somewhere else (Neon, Supabase, your own box). It's the bridge if you want Workers without moving the database. **Vectorize** stores embeddings, so you can build search that matches meaning, or let a chatbot answer from your own content.

## AI and email

**Workers AI** runs open models straight from a Worker, with no GPU for you to look after. The free plan covers 10,000 "neurons" a day, then $0.011 per 1,000 on paid. Per-token prices depend on the model. Llama 3.2 1B costs about $0.027 per million input tokens and $0.20 per million output, and Llama 3.1 8B about $0.045 and $0.38. It's fine for summaries, tagging, embeddings and basic chat. For the very best models you'll still call a different provider, and **AI Gateway** can sit in front to cache and log those calls.

**Email Routing** receives mail for your domain and forwards it or hands it to a Worker. It's free. **Email Sending** sends transactional mail (receipts, password resets) from a Worker. It's the newer of the two, so check its current status, pricing and sending limits in Cloudflare's docs before you build on it.

## What it costs

Every number in this guide comes from Cloudflare's docs as of October 2026, and Cloudflare changes them now and then. Two plans matter here.

| Plan | Price | Fits |
| --- | --- | --- |
| Free | $0 | Learning, side projects, small sites |
| Workers Paid | $5 a month minimum | Real apps, queues, higher limits |

The $5 is per account, not per project. Everything you build shares the included allowances, so ten small apps cost about the same as one.

The classic way to get a nasty bill is a loop. A Worker that calls itself, a cron job that writes far too much, a full table scan on every request. Glance at your usage page every few weeks.

## What I actually run

This site is one Worker. It serves the pages (free and unlimited), takes the email signup form and adds the address to a mailing list, and hands out downloads of the Mac app from an R2 bucket. A rate-limit rule fends off form spam. The directory submission form emails me through the same Worker. One command deploys the lot.

Across my whole account, between 1 and 23 October, I had about 21 Workers, 11 D1 databases, 11 KV namespaces, 5 queues and 11 R2 buckets. In those 23 days they handled about 3 million requests (30% of the included 10 million) and read about 2 billion D1 rows (8% of 25 billion). R2 held roughly 28 GB, mostly backups. Everything sat inside the included amounts except two small overages, R2 storage and one queue. By my own estimate that's well under a dollar on top of the $5. I even started building a monitoring tool for it, because I missed Laravel's Nightwatch and nothing like it existed for Workers when I looked.

Here's what a small SaaS wired together looks like, in one config file.

```jsonc
{
  "name": "my-app",
  "main": "src/index.ts",
  "compatibility_date": "2026-10-01",
  "assets": { "directory": "./dist" },
  "d1_databases": [
    { "binding": "DB", "database_name": "my-app", "database_id": "<id>" }
  ],
  "r2_buckets": [
    { "binding": "FILES", "bucket_name": "my-app-files" }
  ],
  "queues": {
    "producers": [{ "binding": "JOBS", "queue": "my-app-jobs" }],
    "consumers": [{ "queue": "my-app-jobs" }]
  },
  "triggers": { "crons": ["0 3 * * *"] }
}
```

In code you reach each one by the name you gave it in the config, here `env.DB`, `env.FILES` and `env.JOBS`. No connection strings, no passwords to rotate.

![Diagram of one Worker connected to static assets, a D1 database, KV, an R2 bucket and a Queue, with a cron trigger and a visitor sending requests](/assets/img/blog/cloudflare-is-more-than-dns/small-saas.webp)
*One Worker and its bindings. The cron trigger and the queue consumer run as Workers too.*

You don't have to go all in, either. Plenty of people keep a site on an ordinary server and send nightly backups to R2 with rclone. Downloading a restore costs no egress fee, though normal request prices still apply. And a nightly job that pulls from an API is about ten lines: a cron trigger, a `scheduled()` handler, a `fetch()` call and an insert into D1.

## When Cloudflare is the wrong call

A big, write-heavy relational database is the clearest no. D1's one-lane design and 10 GB cap will pinch. Use Postgres and reach it through Hyperdrive.

Existing PHP or Laravel apps are another. Workers don't run PHP, so moving one means rewriting it. Trigv is Laravel, which is why I built a separate Workers version instead of just moving it. For an app like that, Cloudflare for DNS, caching, security and R2 backups is often the sensible limit.

CPU-heavy work (video, large image batches, long jobs) belongs in a container or another service. And the bindings that make everything so easy, `env.DB` and friends, are also what make leaving harder. If you might move later, keep your business logic apart from the Cloudflare parts.

## Where I'd start

Move your DNS over, if it isn't already, and switch on caching and the basic firewall rules. Then try R2 for backups. It's the safest step, because nothing about how your site runs changes. After that deploy one small Worker (`npm create cloudflare@latest`; a contact form is a nice first one), and add D1 only when you've got data to store. Test it locally before you touch the real database. Cron or a queue can wait until you have background work to do.

My [free-tier guide](/blog/launch-web-app-free-on-cloudflare/) walks through the Worker and D1 steps with the exact limits.

Everything here also runs on your own computer with `npx wrangler dev`, which is free and doesn't touch your quotas. Once you have a handful of projects it gets hard to remember which one is on which port and what it's bound to. That's the itch [WorkerDock](/) scratches: a Mac app that finds your Wrangler projects, starts and stops them, shows their logs and draws what each one is connected to.

I'll write more on the pieces I only skimmed: Workers versus a VPS, R2 backups, D1 under load, cron and queues. If you build something on Cloudflare, tell me. I'm on [X](https://x.com/harishchouhan).
