Cloudflare didn't buy Deno. It bought celld
🇧🇷 Leia em Português
On October 9, the Deno team announced that they are joining Cloudflare. The sentence that matters is short:
We will support the Deno runtime for another year with monthly releases containing bug fixes and security updates. After that year we will end our development of the Deno runtime.
Deno Deploy gets six more months. JSR keeps running on Cloudflare’s infrastructure. The runtime stays open source, and they “welcome others who want to continue its development.”
The headlines called it an acquisition. But read Cloudflare’s own post and the Deno runtime isn’t mentioned once. What Cloudflare wanted was celld, a self-hosted implementation of its Workers platform that the Deno team shipped two months earlier.
I contribute to Node.js, so I’ll go through the whole thing: where Deno came from, how Node answered the same problems, the signs that showed up before the announcement, what Cloudflare is getting, and what to do if you run Deno today.
1. Where it started: a talk about regrets
In 2018, at JSConf EU, Ryan Dahl gave a talk called 10 Things I Regret About Node.js. It’s unusual to hear the creator of a runtime list, in public, what he got wrong in it.
The list included:
- Security: any script could read your disk and open sockets. Nothing asked for permission.
- The build system: GYP and the pain of native addons.
package.jsonandnode_modules: a centralized module system and a directory that grows forever.- Module resolution magic:
requirewithout extensions,index.jsresolution. - Dropping promises early: the callback-first core APIs.
At the end of that talk he introduced a prototype called Deno.
Two years later, in May 2020, Deno 1.0 shipped with those regrets turned into defaults: secure by default, TypeScript out of the box, ES modules only, web APIs instead of Node-specific ones, and a single binary with the formatter, linter and test runner inside it.
2. Same problems, different answers
Ryan wasn’t the only one who saw those problems. People inside Node worked on most of them too, on their own track and under a constraint Deno never had: millions of applications already running, which a new release cannot break.
Here is when each runtime shipped its answer:
| Problem | Deno | Node |
|---|---|---|
| Restricting what a script can access | permission flags, 1.0 (2020) | permission model experimental in v20.0.0 (2023), stable in v22.13.0 / v23.5.0, --allow-net in v25.0.0 |
| Running TypeScript without a build step | 1.0 (2020) | type stripping in v22.6.0 (2024), on by default in v23.6.0, stable in v25.2.0 |
Global fetch |
1.0 (2020) | unflagged in v18.0.0 (2022), stable in v21.0.0 |
| Built-in test runner | deno test, 1.0 (2020) |
node:test in v18.0.0, stable in v20.0.0 |
| Watch mode | --watch, 1.4 (2020) |
--watch in v18.11.0 (2022) |
| Single executable | deno compile, 1.6 (2020) |
Single Executable Applications in v19.7.0 (2023) |
Node versions come from the official docs.
Deno shipped first in every row. That’s what a new runtime with no users to protect can do. But a date column only tells you who arrived first. It doesn’t tell you why the other one arrived, and it hides that the answers are different.
The permission model
Deno asks you to run deno run --allow-read --allow-net app.ts. Node lets you run node --permission --allow-fs-read=./data app.js. The two look alike on the command line.
Underneath, they make different promises. Node’s security work on code loading goes back to the experimental policy manifests in v11.8.0, in 2019, and the permission model came out of that same security track. The docs say plainly what it is:
The permission model implements a “seat belt” approach, which prevents trusted code from unintentionally changing files or using resources that access has not explicitly been granted to. It does not provide security guarantees in the presence of malicious code.
That’s a deliberate design choice. Node trusts the code it is asked to run, as its security policy states, and the permission model is built for limiting the damage of mistakes in that code. It isn’t a sandbox.
TypeScript
Deno ran .ts files from day one by compiling them.
Node took a narrower path: type stripping. It replaces type annotations with whitespace and runs what’s left. There’s no tsconfig, no source maps and no type checking, and only erasable syntax is allowed. No enum, no namespace with runtime code.
That constraint went upstream. TypeScript 5.8 added the --erasableSyntaxOnly flag precisely so your editor warns you about code Node’s type stripping can’t run. Node’s design changed the language’s own tooling.
Web APIs
Deno didn’t invent fetch. WHATWG did. Same for Web Streams, Web Crypto, WebSocket, structuredClone.
What Deno did was make the browser APIs the default surface of a server runtime. Node got there through its own work: fetch runs on undici, an HTTP client built inside the Node project.
And the runtimes don’t converge by accident. They sit at the same table. Node, Deno, Cloudflare Workers and others started coordinating in WinterCG, a W3C community group. In 2025 that work moved to Ecma as TC55, nicknamed WinterTC: a technical committee for web-interoperable server runtimes.
Its main deliverable is the Minimum Common API, the subset of the web platform that every server runtime agrees to support. When fetch, URL, TextEncoder or AbortController behave the same in Node and in Deno, that’s not one runtime copying the other. It’s a spec they wrote together.
Code that runs on Node, Deno, Bun and Cloudflare Workers without changes is common today. In 2018 it wasn’t. Every runtime involved, Node included, did part of that work.
The tooling
Deno ships deno test, deno fmt and deno lint in one binary. Node added a test runner and a watch mode, and left formatting and linting to the ecosystem, where Prettier, ESLint and Biome already do that job.
Different philosophies. Deno bets on one opinionated binary. Node bets on a small core and a big ecosystem.
The pattern
Look at the four cases together. Every one of Node’s answers is opt-in or non-breaking. The permission model is off unless you pass --permission. Type stripping only runs erasable syntax. fetch arrived next to the existing http module, not in place of it. Nobody had to rewrite an app to get any of it. An upgrade was enough.
That’s the constraint at work, and it also explains why the product didn’t survive.
3. What didn’t survive - and what flowed the other way
Not every idea won, including inside Deno itself.
URL imports. Deno’s original pitch was import { serve } from "https://deno.land/std/http/server.ts". No package.json, no node_modules, no registry.
Node shipped network imports behind an experimental flag and later removed the flag. Deno itself added npm: specifiers in 2022, then package.json support, then built JSR - which is, in the end, a registry.
No node_modules. Deno 2.0, in 2024, was largely about Node compatibility: node: built-ins, npm packages, package.json, node_modules. A runtime born from a talk that listed node_modules as a regret ended up supporting it, because that’s where the packages were.
So the ideas flowed both ways. Deno pushed the whole ecosystem toward web standards and secure defaults. Node’s ecosystem pulled Deno back toward npm.
That’s the turning point, I think. Once your selling point becomes “it runs your Node code, but better”, you’re competing on Node’s field, against a runtime that keeps improving on its own.
4. The signs were there
None of this came out of nowhere. Read in order, the public record looks like a slow countdown.
2023: package.json. Ryan Dahl had to explain in public why the runtime born from his regrets talk was adopting package.json. The ecosystem had won that argument.
2025: the infrastructure shrinks. Deno Deploy started in 25 regions, grew to 35 and dropped to 6. Developers noticed - the regions, Fresh without a release since October 2024, Deno KV with no new version since 2023. Ryan answered in May 2025:
It started in 25 regions, grew to 35, and now runs in 6. That reduction was driven by cost, yes - but also by usage.
In the same post, Deno KV “will remain in beta”, with fixes only for critical bugs and security issues. And the closing line:
We’re not winding down. We’re winding up.
Seventeen months later, the team joined Cloudflare.
NOTA: every founder defends the company in public until the day they can’t. That’s part of the job. Still, the cost-cutting showed up in the product long before it showed up in the announcement.
5. What Cloudflare is actually getting
The obvious reading is that Cloudflare hired a team and shut down a competitor. I think that’s half right.
First, who ends what. The line about ending the runtime is in Deno’s post. Cloudflare’s post, signed by Kenton Varda and Ryan Dahl, doesn’t mention the runtime at all, and neither post uses the word “acquisition”. The team is joining. The runtime is what doesn’t come along.
Second, Deno had already changed direction before any of this. On August 5, 2026, the team shipped celld 0.1.0. Ryan describes it as “one binary, written in Rust, with object storage as its only external service dependency”. It’s an implementation of Cloudflare’s Workers and Durable Objects model that you run on your own servers.
Two months before the announcement, Deno’s newest product was a compatible implementation of a Cloudflare product. Kenton Varda explains the interest from Cloudflare’s side:
Here was an implementation of Workers and Durable Objects designed to be fully compatible with our own, while being focused on being self-hostable and scalable.
That’s the piece Cloudflare didn’t have. workerd, the runtime under Workers, is open source, but running it yourself was never a first-class path. According to the post, Ryan and Bert Belder will lead “a new effort to make workerd self-hosting a first-class supported way to build and run apps using the Workers programming model”, merging code and ideas from celld into workerd.
So yes, it’s a hire to work on the Workers runtime. The question is why Cloudflare wants that specific work, and the post answers it with a story:
When we originally pitched Workers for Platforms to customers like Shopify in 2022, the feedback was clear. They couldn’t possibly build on us unless the Workers Runtime was open source.
Cloudflare also takes on the lock-in accusation directly: “we have former customers that migrated away from us by using workerd. And that’s OK!”
Shashi Bellamkonda sums up the business side: “Some of Cloudflare’s largest customers have wanted a way to run its software on their own servers”, and “Buyers care about the option to leave.”
A platform you can only run on one vendor’s servers is a hard sell to a big company. One you can also run on your own machines has an exit door, and an exit door makes it easier to walk in. Open-sourcing workerd opened the door. celld is what makes it practical to walk through.
And the end of the Deno runtime follows from the same math. A team of that size can’t maintain Deno, Deno Deploy and a self-hosted workerd at the same time. They chose the one they believe in now.
A Worker is a function that receives a Request and returns a Response, the same web-standard APIs that WinterTC standardizes. The team isn’t changing its toolbox, only the product it builds with it.
6. Why it ended
Picture a small restaurant that opens on a street full of old, established places. It has a better menu: lighter, cleaner, without the stuff everyone complains about.
But the old places aren’t standing still. Their regulars have been asking for lighter food too, and their kitchens start changing, each in its own way and at its own pace.
The customers never have to change their route. The food gets better at the place they already go to, with the parking lot they already know. The new restaurant closes.
That’s Deno, and it’s the pattern from section 2 seen from the other side. The cost of switching runtimes is huge: dependencies, deployment, hiring, years of muscle memory. Very few teams pay that cost for a better default. They wait for the default to arrive where they already are.
There’s a second reason, and it has nothing to do with which runtime is better: who pays for each one.
Deno was a company. Deno Land Inc. raised a seed round and a Series A, and the runtime was free. The money had to come from the products around it: Deno Deploy, Subhosting, Deno KV. That’s the usual deal for a VC-backed open source startup. The project brings the users, and the hosted product has to pay the bills before the investment runs out.
Node is held by the OpenJS Foundation. No company owns it. That wasn’t always true: Joyent owned it until the io.js fork in 2014 forced the move to a foundation the following year.
Each model buys something and pays for it.
The company model bought Deno speed and coherence. A small team with one vision shipped permissions, TypeScript and web APIs together, in 2020, without asking anyone. Ryan put it bluntly on The Changelog in 2024, when asked why he didn’t put that effort into Node instead:
I’m not willing to sit in committee for 13 years trying to make [what we built into Deno by default] happen.
The foundation model buys Node something less exciting: it doesn’t depend on a funding round. Nobody can decide one day that it’s over. The price is speed. Every change goes through review and consensus, and has to keep its promise to the people already running Node in production.
Neither model could have done the other’s job. A foundation would never have bet on a runtime with no node_modules. A startup was never going to keep a 17-year-old runtime running on millions of servers without breaking them.
7. What to do if you run Deno today
- Deno Deploy: you have six months. Start the migration now. Cloudflare is offering help to paying customers.
- Deno runtime in production: you have one year of security fixes. Migrate to Node.
- JSR packages: JSR keeps running. You’re fine for now.
- Your code: if you followed Deno’s style (web APIs, ES modules, TypeScript), most of it already runs on Node.
NOTA:
node --permission --allow-fs-read=. app.tsruns a TypeScript file under the permission model, withfetchglobal andnode --testone command away. In 2020, only Deno could run something like that.
The runtime has one year of fixes left, and then it’s over. If you run Deno, migrate to Node.
Thanks for reading!