Posted 9 min read
Why we build on Cloudflare
Most infrastructure is a dozen providers stitched together. Ours is one network, from the domain name to the model call, and it is Cloudflare's.
EngineeringInfrastructureAI

On this page
- One network, not a catalogue of services
- Everything in code, through OpenTofu
- The front door: domains, DNS and the CDN
- Compute and storage
- Workers: code that runs everywhere
- R2: storage without egress fees
- People and live media
- Access: private tools without a VPN
- Realtime: calls without media servers
- Agents and models
- Agents: stateful AI on Durable Objects
- AI Gateway: one way in to every model
- Source code: Git on the same platform
- Standing on our own, on a shared network
- What it costs us
- The network we want under everything
Every Foretag company needs the same things underneath it: a domain, DNS, a CDN, somewhere to run code and somewhere to keep files, a way to keep internal tools private and, more and more, live audio and video, AI models and agents. The usual answer is a provider for each, with a contract, a dashboard, a set of credentials and a failure mode apiece.
Ours is one network. We build on Cloudflare: our domains are registered, resolved, cached and served there, most of our apps run on Workers in every one of its locations, and our files, calls, agents and model traffic share the same platform, with our source code on its way there. What follows is what we use, why one network beats a catalogue of services, and what the choice costs us.
One network, not a catalogue of services#
Most clouds are a catalogue: separate services in separate regions, joined by configuration you write and credentials you manage. Cloudflare is built the other way round. Its products run on the same machines, in the same data centres, across hundreds of cities, so the request that resolves a domain, the cache that serves a page, the check on who is asking and the code that answers all happen close to the person using the app, on one network.
| What we use | What it does for us | What it replaces |
|---|---|---|
| Registrar | Domains, at cost | A separate registrar |
| DNS | Authoritative DNS, with DNSSEC | A separate DNS provider |
| CDN | Caching and TLS at the edge | A separate CDN |
| Workers | Apps, APIs and static sites | Servers, or serverless in one region |
| R2 | Files and media, with no egress fees | Object storage billed for every download |
| Access | Internal tools behind identity | A VPN |
| Realtime | Audio, video and data over WebRTC | Media servers we would run ourselves |
| Agents | Stateful AI agents | A server, a database and a scheduler for each agent |
| AI Gateway | One way in to every model | A key, a dashboard and a retry policy per provider |
| Artifacts | Git repositories | A separate Git host |
The point is not the length of that list. It is that everything on it shares one account, one API, one set of identities and one configuration model, so a new company starts with all of it on its first day rather than assembling it over its first year.
One network, from the domain name to the model call.
Everything in code, through OpenTofu#
None of it is set up by hand. The platform is provisioned with OpenTofu, the open source fork of Terraform, through Cloudflare's own provider: every zone and DNS record, the TLS settings, and every Access application with the policy in front of it. Each app's Worker, with its routes and bindings, is declared in a Wrangler configuration beside its code, so everything we run on Cloudflare, from the zone to the binding, lives in version control.
resource "cloudflare_zone" "shop" {
account = { id = var.account_id }
name = "example.com"
}
resource "cloudflare_dns_record" "mail" {
zone_id = cloudflare_zone.shop.id
name = "example.com"
type = "MX"
content = "mx.example.net"
priority = 10
ttl = 1
}
That changes what working on infrastructure feels like. A new company's domain, records and access arrive as a reviewed change rather than an afternoon in a dashboard, the plan shows exactly what will change before anything does, and the repository is a complete and current description of what exists. When something looks wrong, the question is never who clicked what, only which commit.
The front door: domains, DNS and the CDN#
A company's domain is the one piece of infrastructure it can never move away from, so it should sit somewhere boring and trustworthy. Cloudflare Registrar sells domains at cost, at the registry's price with no markup, and turning on DNSSEC takes one click because the registrar and the DNS are the same system.
DNS is authoritative on the same anycast network as everything else, and a proxied record puts the CDN, TLS and caching in the path without another provider in between. When a Worker claims a hostname as a custom domain, Cloudflare creates the DNS record and issues the certificate itself. A new app goes from a name to a live, certificated site in a single deploy:
{
"name": "storefront",
"main": "src/worker.ts",
"compatibility_date": "2026-09-01",
"routes": [{ "pattern": "shop.example.com", "custom_domain": true }],
"assets": { "directory": "./dist/client", "binding": "ASSETS" },
"r2_buckets": [{ "binding": "MEDIA", "bucket_name": "storefront-media" }],
"ai": { "binding": "AI" },
}
Compute and storage#
Code and files are where an app spends its life, and both run on the same network that answers its domain.
Workers: code that runs everywhere#
Workers run in V8 isolates rather than containers or virtual machines. An isolate starts around a hundred times faster than a Node.js process in a container and uses an order of magnitude less memory, which is what lets the same code run in every Cloudflare data centre instead of in a region we have to choose. There is no fleet to size and no region to fail over from.
Our sites are prerendered and served as static assets, and requests for static assets are free and unlimited: a Worker runs only for the requests that need code. Everything else a Worker touches arrives as a binding, declared in its configuration:
export default {
async fetch(request, env) {
const { pathname } = new URL(request.url);
if (pathname.startsWith('/media/')) {
const file = await env.MEDIA.get(pathname.slice('/media/'.length));
if (!file) return new Response('Not found', { status: 404 });
return new Response(file.body, {
headers: { etag: file.httpEtag },
});
}
return env.ASSETS.fetch(request);
},
} satisfies ExportedHandler<Env>;
There is no access key in that code, and nothing to leak. A binding is a capability the platform hands the Worker, not a credential the Worker has to keep, so storage, queues, models and state are reachable from exactly the code that was given them, and from nowhere else.
Capabilities, not credentials.
R2: storage without egress fees#
R2 is object storage with an S3-compatible API, so existing tools and SDKs work unchanged, and a Workers binding for everything else. Data read out of it carries no egress charge.
That matters more than it sounds. For companies in travel and retail, images and media are what customers download most, and where egress is billed by the gigabyte, that is the part of the bill that grows with success. With R2, the bill follows what we store and the operations we make, not how popular the product becomes.
People and live media#
Some traffic is people rather than pages: colleagues reaching internal tools, and customers on a call.
Access: private tools without a VPN#
Internal tools sit behind Cloudflare Access. Every request to a protected application is checked against its policy at the edge, before it reaches the application, and the application receives a signed token in the Cf-Access-Jwt-Assertion header that it verifies for itself.
There is no network to join and no VPN client to install. Each application has its own policy, so access to one tool implies nothing about another, and a person's access follows their identity rather than the network they happen to be on. Like everything else here, each application and its policy is declared in OpenTofu.
Realtime: calls without media servers#
Live audio and video are hard to run well. They need media servers close to every participant, relays for the networks that block direct connections, and capacity for the moment everyone joins at once.
Cloudflare Realtime removes all of that. Its SFU is a WebRTC selective forwarding unit that runs on Cloudflare's network, so there are no SFU servers to deploy and no region to pick, and its TURN relays use anycast too. Our application decides who may publish and who may subscribe; Cloudflare forwards the media. RealtimeKit adds SDKs, prebuilt interfaces, recording and transcription on top, for the products that want a meeting rather than a media pipeline.
Agents and models#
AI adds two needs of its own: programs that remember and act over time, and a controlled way to reach the models they call.
Agents: stateful AI on Durable Objects#
An AI agent is not a request and a response. It remembers a conversation, works on something for minutes or days, wakes up on a schedule, and talks to the people using it in real time. On most platforms that means a server, a database, a queue and a scheduler for every agent.
With the Agents SDK, an agent is a Durable Object: a single, globally addressable instance with its own SQLite storage. State set on it persists and synchronises to connected clients over WebSockets, and schedules, one-off or recurring, are part of the class:
import { Agent } from 'agents';
type State = { notes: string[] };
export class Assistant extends Agent<Env, State> {
initialState: State = { notes: [] };
async remember(note: string) {
this.setState({ notes: [...this.state.notes, note] });
// Come back to it tomorrow.
await this.schedule(60 * 60 * 24, 'followUp', { note });
}
async followUp({ note }: { note: string }) {
// Runs a day later, in this same agent, with its state intact.
}
}
Our AI assistants are built this way. Each one is its own small, durable program that runs only while it has something to do and keeps its memory in between, rather than a row in a database that a fleet of servers has to keep finding.
AI Gateway: one way in to every model#
Our model calls go through AI Gateway. Models hosted on Workers AI and models from third party providers are reached through the same binding, and the gateway adds what every AI product needs and nobody wants to build twice: logs and analytics for requests, tokens, latency and cost; caching for identical requests; rate limits and spend limits; retries and fallbacks when a provider fails; and guardrails and data loss prevention on what goes in and comes out.
const answer = await env.AI.run(
'@cf/meta/llama-3.3-70b-instruct-fp8-fast',
{ messages: [{ role: 'user', content: question }] },
{ gateway: { id: 'default', cacheTtl: 3600 } },
);
Because the policy lives in the gateway rather than in each app, changing a fallback, a rate limit or a budget is a configuration change, not a release.
Source code: Git on the same platform#
Artifacts is Git-compatible, versioned storage: repositories that any Git client can clone and push over HTTPS, that Workers can create and manage through a binding, and that emit events, such as a push, to a queue. It is new, in beta, and built for a world where agents write code as well as people.
It is where our source code is heading. A repository becomes one more resource on the platform, and the automation that follows a push is an ordinary Worker reading from a queue, not a separate CI product with its own accounts and secrets.
Standing on our own, on a shared network#
Our manifesto asks us to stand on our own, and building on one provider can look like the opposite. We do not think it is. Standing on our own does not mean running our own network; it means never being unable to leave.
The clearest case is the runtime. workerd, built from the same code that powers Workers, is open source under the Apache 2.0 licence, and it runs as an application server anywhere: on a laptop, in a test, or on machines of our own. That cannot be said of AWS Lambda, or of many other serverless platforms, whose runtimes exist only inside their provider's cloud; Lambda's emulator is for testing, not for serving traffic. A Worker we write today could be served from our own hardware tomorrow. It would leave the network behind, but not the code.
The rest of the platform leans the same way. R2 speaks the S3 API, Artifacts speaks Git, Realtime speaks WebRTC, AI Gateway speaks the same API shapes the model providers do, and DNS is DNS. The description of everything we run is OpenTofu, an open source tool, so even the map of our infrastructure is ours. Our data lives in SurrealDB, not in any provider's proprietary store.
Nor does everything belong on Workers, and we do not pretend it does. Stateful applications, the ones that need long lived processes and disks of their own, run on Kubernetes clusters we operate ourselves, provisioned with the same OpenTofu and reached through the same front door. Cloudflare is where our code meets the world, not the only place it runs. What we build there runs best there, but none of it is trapped there.
What it costs us#
No choice is free, and this one has costs we have chosen to carry:
- One provider, one blast radius. When Cloudflare has a bad day, so does every company we run. We accept that because one well run network fails less often than the seams between many, and because our code and data are kept in forms that can move.
- Durable Objects are Cloudflare's own model. Code written against Durable Objects and the Agents SDK is the least portable code we have, so we keep it thin and keep the logic that matters in plain TypeScript around it.
- Some of it is young. Artifacts is in beta. We adopt new products where we can afford to be early, and not where we cannot.
The network we want under everything#
Infrastructure is at its best when nobody has to think about it. Cloudflare gives every Foretag company a domain, DNS, a CDN, compute that runs everywhere at once, storage without egress fees, private tools without a VPN, calls without media servers, agents with memory, one way in to every model and a home for its source code, on one network, declared in code, with one account and one way of working.
That is the ground we want to build on.
Written by
- CB
Chiru Boggavarapu
Founder & Chief Executive