OpenOrange: Run Cloudflare Workers on Your Own Servers
OpenOrange runs your existing Cloudflare Workers and Durable Objects on AWS, GCP, Azure, or your own VPS. I installed it and tested dev and deploy. Here's how it works, what it costs, and where it breaks.

Quick result
pnpm add -g open-orange && orange dev → your Worker answers on :8787, with every Durable Object running as a Rivet Actor on its own SQLite file.
Disclosure: this guide contains referral links. If you buy through them, I may earn a commission at no extra cost to you.
Cloudflare Workers is a great platform until it isn’t. The free tier is generous, the paid tier is cheap at small scale, and everything works right up to the moment you hit a wall: 128 MB of memory per Worker, 30 seconds of CPU per request, 10 GB per Durable Object, and a nodejs_compat shim standing between you and the real Node.js. Then comes the second discovery. Durable Objects keep their state inside Cloudflare, and there is no export button.
OpenOrange, launched by Rivet on November 12, is the cleanest exit ramp I have seen so far. It is an Apache-2.0 runtime that takes your existing Worker project, wrangler.jsonc and all, and runs it on AWS, Google Cloud, Azure, or a box under your desk. wrangler dev becomes orange dev. Durable Objects become Rivet Actors, each with its own SQLite database. No code changes.
I installed it, ran a Durable Object on my own machine, and walked the deploy path. Short version: the promise mostly holds up, which is more than I expected from a repo that is two days old.
What OpenOrange is
OpenOrange is a CLI (orange, installed from npm as open-orange) plus a runtime that implements the Workers API on plain Node.js. It does not run workerd, Cloudflare’s open-source runtime. It reads your Wrangler config, maps every binding onto Rivet’s actor infrastructure, and reuses Cloudflare’s own MIT-licensed code where it can (the Workflows engine, for example).
Rivet, the company behind it, builds actor-based infrastructure: long-lived, stateful processes with their own storage. That turns out to be almost exactly what a Durable Object is, which makes OpenOrange less of a port and more of a marriage between two very similar ideas.

Why run Workers off Cloudflare at all
Two reasons, and they are the same two reasons everything gets self-hosted: limits and money.
The limits. On Workers Paid, every Worker and Durable Object gets 128 MB of memory and 30 seconds of CPU per request (5 minutes if you raise the cap). Cron, queue, and alarm handlers get 15 minutes of wall time. A request can hold 6 open connections. A Durable Object tops out at 10 GB of storage, and your bundle can’t exceed 64 MiB. Under OpenOrange, every one of those becomes “whatever your machine allows”, plus real Node.js instead of the nodejs_compat subset.
| Limit | Cloudflare Workers Paid | OpenOrange |
|---|---|---|
| Memory per Worker / Durable Object | 128 MB | Your machine’s RAM (~4 GB Node heap default, configurable) |
| CPU time per request | 30 s default, 5 min max | No cap |
| Cron, queue, alarm handlers | 15 min wall time | No cap |
| Open connections per request | 6 | OS limits |
| Storage per Durable Object | 10 GB | No cap |
| Worker bundle | 64 MiB | No cap |
| Node.js | nodejs_compat subset |
Stock Node.js |
If you have read our list of self-hosted apps that run on Cloudflare Workers or followed the EmDash deploy guide, you know how quickly those numbers start to matter.
The bill. Cloudflare meters Durable Object compute, storage, and SQLite writes separately, and the metering is where the money goes at scale. Rivet’s own cost model, priced against raw AWS Graviton with 40% headroom plus S3 in your account:
| Cloudflare | OpenOrange | ||
|---|---|---|---|
| Durable Object compute | $0.045 per GB-hour | $0.0094 per GB-hour | 4.8× cheaper |
| Durable Object storage | $0.20 per GB-month | $0.023 per GB-month | 8.7× cheaper |
| SQLite writes | $1.00 per million rows | ~$0.18 per million | 5.7× cheaper |
The third reason is quieter but worth naming: exit cost. Durable Objects trap state per-object inside Cloudflare. If you ever want to leave, you are writing a bespoke export. OpenOrange comes with orange migrate, which moves those objects while your app keeps serving. More on that below.
Try it locally (what I ran)
You need Node.js 22 or later, pnpm, and a Worker project with a wrangler.jsonc, wrangler.json, or wrangler.toml. Docker only enters the picture when you deploy.
Install the CLI globally:
pnpm add -g open-orangeThen, inside your Worker project, replace wrangler dev with:
orange devI used the repo’s hello-durable-object example, a tiny counter Worker where each name gets its own Greeter Durable Object with a SQLite visits table. The output on my machine:
🍊 open-orange v1.0.1 · by rivet.dev
Worker hello-durable-object
Config wrangler.jsonc
Compat date 2026-09-03
Binding Resource Runs on
env.GREETER Durable Object Rivet Actor · Greeter
● Ready in 3.0s
Local http://127.0.0.1:8787
Agent Run orange mcp to connect your coding agent to the Rivet MCPTwo requests later, the proof that state actually persists:
$ curl http://localhost:8787/Devin
{"greeting":"Hello, Devin!","visits":1}
$ curl http://localhost:8787/Devin
{"greeting":"Hello, Devin!","visits":2}The counter incremented. That is the interesting part, not the greeting. The Greeter Durable Object ran as a Rivet Actor, its SQLite file landed in a local .open-orange/ directory, and the same name routed to the same object on the second request, exactly like Cloudflare’s getByName semantics.
A few other commands worth knowing before you start:
orange doctorscans the project, checks compatibility, and lists every binding with what it runs on under OpenOrange.orange typesgeneratesworker-configuration.d.tsfor your bindings.orange d1 executeandorange d1 migrations apply|listhandle D1 databases directly.orange mcpconnects a coding agent (Claude Code, Codex, Cursor) to the Rivet MCP so it can run the quickstart for you.- Wrangler commands Orange doesn’t implement, like
deleteorsecret, print “is not implemented”. Run them with realwrangler.
Deploy it somewhere
Three targets, depending on how much you want to operate:
- Rivet Cloud — a dedicated, fully managed cluster on AWS or Google Cloud in your chosen region.
orange login,orange link --cluster <name>,orange deploy, done. You get a URL. - BYOC — your AWS, GCP, or Azure account, operated by Rivet over an outbound connection.
- Self-hosted — Apache-2.0, runs anywhere Docker runs: Hetzner, on-prem, an air-gapped rack, your homelab.
I ran the self-hosted path as far as it goes without a live cluster. orange deploy --dry-run builds nothing and calls no API; it writes a Dockerfile and build context you can inspect:
$ orange deploy --dry-run --outdir out
Dry run. Nothing was built or sent to Rivet Cloud.
Dockerfile /tmp/hello-durable-object/out/Dockerfile
Context /tmp/hello-durable-object/out/context
Image hello-durable-object:20261114-071535
Self-host
docker build --platform linux/amd64 -f out/Dockerfile -t hello-durable-object:20261114-071535 out/context
docker run --rm -p 8080:8080 -e 'RIVET_ENDPOINT=https://<namespace>:<token>@<engine-host>' hello-durable-object:20261114-071535The generated Dockerfile is refreshingly boring: node:24-slim, a pnpm install --prod, your Worker code copied in, and orange serve as the entrypoint on port 8080. No magic. Any container host can run it.
One thing the dry run makes clear: your containers don’t get inbound connections from a dashboard somewhere. Each node runs orange serve and connects out to a Rivet Engine endpoint (RIVET_ENDPOINT), which is the control plane that places Durable Objects on containers and routes requests to them. You run that control plane yourself too. Rivet publishes per-platform guides for it: Kubernetes, AWS ECS, Docker Compose, Hetzner, Railway, Render, or a plain VM.

So a realistic minimal self-host rig is two pieces: one machine (or compose project) running the Rivet Engine control plane, and one or more containers running your built image. For the box itself, a cheap VPS is plenty. We compared the usual suspects in DigitalOcean vs Vultr vs Hetzner, and my own pick is Hetzner at roughly €4-5/month.
Try Hetzner CloudHow it works under the hood
The architecture is simpler than the pitch suggests:

Three moving parts:
- Your containers run your Workers and Durable Objects on stock Node.js, anywhere you like.
- The Rivet control plane (self-hosted, Rivet Cloud, or BYOC) assigns each Durable Object to a container and routes requests to it.
- S3 stores every object’s SQLite data, behind Rivet’s WarpDrive layer with SSD in front. Scaling out means adding machines, and an S3-compatible store (MinIO, or even R2 itself — we use it for Dokploy backups) works as the backing store.
Because state lives in S3 rather than on the container’s disk, objects can move between machines without losing anything. That property is what makes the next part possible.
Moving your data off Cloudflare
This is the feature that elevates OpenOrange from “interesting” to “worth paying attention to”. orange migrate moves live Durable Objects and D1 databases out of Cloudflare with no downtime. The sequence, per object:

The Cloudflare-side object keeps answering requests while it ships its SQLite data over in pages. If no write arrives mid-copy, it marks itself moved and starts answering migrator.moved, which routes requests to the OpenOrange copy. Rollback is pointing DNS back and setting MIGRATOR_DISABLED=true. It is a clever design, and it sidesteps the thing that usually makes Workers lock-in permanent.
What works and what doesn’t
Not every binding survives the trip. The docs rate each one honestly:
| Cloudflare binding | Status | Runs on |
|---|---|---|
| Durable Objects | Partial | One Rivet Actor per object, own SQLite db |
| KV, D1, Rate Limiting | Drop-in | Rivet Actors |
| Queues, Cron Triggers | Drop-in | Rivet Actors |
| Cache | Partial | One Rivet Actor per cache name |
| Workflows | Partial | Cloudflare’s own engine, one Actor per instance |
| Tail Workers | Partial | Rivet Durable Streams |
| R2 | Pluggable | Rivet Actor, or S3 / GCS / R2 / MinIO |
| Workers AI | Pluggable | OpenRouter, OpenAI, any compatible endpoint |
| Send Email | Pluggable | Amazon SES or any SMTP |
| Browser Rendering | Pluggable | Browserbase or any CDP endpoint |
| Hyperdrive | Drop-in | Your Postgres/MySQL, direct connection |
| Sandbox | Pluggable | E2B, Daytona, Freestyle, agentOS, or local dir |
| Analytics Engine | Not supported | writeDataPoint() silently drops data |
| Vectorize, Images, Stream, Pipelines, AI Search, mTLS, VPC | Not supported | Calls throw |
“Pluggable” means you configure a provider, which is quietly one of the best parts: Workers AI becomes any OpenAI-compatible endpoint, email becomes any SMTP server, R2 becomes any S3. The Cloudflare-specific service turns into a generic equivalent you already know how to run.
Watch the silent failure
Most unsupported bindings throw an error when called, so you find out immediately. Analytics Engine is different: writeDataPoint() accepts your data and drops it. If you rely on Analytics Engine for metrics, audit that before migrating, not after.
The honest caveats
The project is two days old, so expect rough edges. The docs are at least honest about them. A few things to keep in mind:
- Durable Objects are rated “partial”, not “drop-in”. The known differences are documented per-binding. For a counter demo, everything worked; for a production object doing exotic
ctx.storagecalls, read the differences page first. - You lose the edge network. Cloudflare runs your Worker in 300+ cities, OpenOrange runs it where you put it. If your users are global and latency-sensitive, that trade is real.
- You are operating servers again. Someone has to run the Rivet Engine, an S3 store, and the containers. That is the entire point of self-hosting, but it is still work, and a self-hosted PaaS or a plain box plus systemd will not manage itself.
- Dev and production share the runtime, which is a genuine strength, but the control plane adds a moving part local dev doesn’t have. Test against a real
RIVET_ENDPOINTbefore you bet a migration on it.
Should you use it
If you have a Workers project that is starting to bruise against Cloudflare’s limits, or a Durable Object workload where the meter is running faster than the revenue, OpenOrange is worth an afternoon. orange dev on an existing project takes about ten minutes to evaluate, and it does not touch your code.
If your Workers run fine on the free tier, stay. Cloudflare’s free tier is legitimately hard to beat, and this site’s own Plausible-through-Workers proxy has run on it for years without complaint. The case for OpenOrange appears at the moment you need more than 128 MB, more than 30 seconds, stock Node.js, or a way out.
What I find most promising is the direction. Rivet built a credible self-hosted runtime for the Workers ecosystem, much like self-hosted Convex pulled off for real-time backends. Same bet, different platform: keep the DX, lose the landlord.
FAQ
Does OpenOrange require changes to my Worker code?
No. It reads your existing wrangler.jsonc/wrangler.toml and runs the same code. Bindings map to Rivet equivalents automatically. The exceptions are bindings in the “not supported” rows of the table above, which throw or drop data and need attention before a real migration.
Is it just workerd self-hosted?
No. OpenOrange implements the Workers API on stock Node.js (and can use Bun) and maps Cloudflare services onto Rivet’s actor system. It uses Wrangler only to read your config. That is why the runtime limits go away: there is no workerd sandbox underneath.
Is OpenOrange free?
The runtime is Apache-2.0 and self-hosting costs you only the machines it runs on. Rivet Cloud and BYOC are the paid, operated options if you don’t want to run the control plane yourself.
Can it fully replace Cloudflare Workers?
For Workers + Durable Objects + KV/D1/Queues/Cron workloads, mostly yes, with “partial” caveats documented per binding. For anything touching Vectorize, Images, Stream, AI Search, or Analytics Engine, no, not yet. Check the bindings table against your wrangler config before planning a move.
What about Cloudflare's global edge network?
You give it up. OpenOrange runs in the regions you deploy to. Fronting your self-hosted cluster with a CDN restores some of the reach, but sub-50ms-everywhere is a Cloudflare property, not a runtime property.
Closing
OpenOrange does the one thing Cloudflare can’t offer: a way out that doesn’t require rewriting the app. The CLI is real, the dev loop works today, orange migrate solves the genuinely hard part of leaving, and the whole thing is Apache-2.0. It is also brand new, so treat “partial” as “verify before you trust”.
Ten minutes with orange dev on your own project will tell you more than this article can.
Did this guide work for you?
Need help with Astro, VPS, or a self-hosted app? View services →


