celld: self-hosted Durable Objects without a consensus service
self-hosted, distributed Durable Objects
At a glance
- What is it?
- celld is a Rust daemon that runs a Cloudflare Workers application on your own machines, with each Durable Object living as a cell backed by an S3-compatible bucket. The design trades single-node write latency for a fleet that needs no membership protocol.
- Who is it for?
- Adopt celld if you already have a wrangler.json and want Durable Objects, KV, Queues, D1 or Workflows running on hardware you control, with state in an S3-compatible bucket you already pay for. Do not adopt it if your application depends on the Cloudflare network, a GPU, or a browser farm; the README states those are out of scope, and the compatibility page lists the remaining gaps.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What celld solves, and who it is for
Cloudflare Durable Objects give you a named, single-threaded server with its own storage, addressed by an ID and reachable from a Worker. Running that model outside Cloudflare has usually meant either reimplementing the coordination layer yourself or accepting a single machine. celld is an open-source daemon that runs a Cloudflare Workers application on your own machines: Workers, Durable Objects, KV, Queues, D1, R2, Workflows, Cron Triggers, and static assets, deployed from the wrangler.json you already have.
The intended user is a team that already writes Workers code and wants the same programming model on infrastructure it operates. According to the README, each object is a cell: a named server with its own SQLite database, and long-term state lives in your S3-compatible, Google Cloud Storage, or Azure Blob Storage bucket. That is the whole pitch. You keep the wrangler.json, you keep the binding APIs, and you supply the machines and the bucket instead of a Cloudflare account.
The README is explicit about the boundary. A product that needs the Cloudflare network, a GPU, or a browser farm is out of scope, and the Cloudflare compatibility page lists each gap in the supported services. Containers are marked experimental. If your application leans on a Cloudflare feature outside the programmatic platform, celld is the wrong tool and the compatibility page is where you confirm that.
How a fleet coordinates without consensus
A node is one celld process, and you run one on each machine. Every node embeds V8 and executes Wrangler bundles. The nodes that share one bucket are a fleet, and that bucket holds three things: the deployments, the cell state, and small ownership records.
Ownership is the interesting part. A conditional bucket write gives a node ownership of a cell, so exactly one node owns a cell at a time. There is no membership protocol, no failure detector, and no consensus service. The bucket is the arbiter, and its conditional write semantics are what the model rests on. That is a real dependency: if your object store does not implement conditional writes the way celld expects, the ownership guarantee does not hold, and the README does not present a fallback.
Data flow for writes runs through the LTX replication format. celld captures each committed SQLite write in LTX. A single node proves the write durable by uploading that data to the bucket. A fleet of two or more nodes proves it sooner: the owner sends the data to one or two other nodes, and the write is durable as soon as they hold it on their disks. celld uploads to the bucket afterwards. Signed peer HTTP carries both routing and the replicated-log transport.
The consequence for a single-node deployment is stated plainly in the README. A single node has no other node to send to, so each write waits for the bucket, and those writes are much slower. You do not get the fast path until you run at least two nodes. Before a takeover restores a cell, celld recovers an open log from the prior owner, so the bucket holds long-term state and nodes stay replaceable. The guarantees document is where the complete protocol lives.
Installing celld and running the first Worker
The installer downloads the celld binary, and the README notes that provenance is verifiable with gh attestation verify. It keeps each release under ~/.local/lib/celld/releases and points one symlink at the current one.
curl -fsSL https://celld.dev/install.sh | shPut ~/.local/bin on your PATH if the installer asks you to. Worker projects deployed with celld deploy need esbuild on PATH; asset-only projects do not. To remove celld later, delete the symlink and the releases:
rm `which celld` && rm -rf ~/.local/lib/celldFor a local run you do not need Docker or a cloud bucket. celld dev starts one node with a local object store, and the Worker listener uses http://127.0.0.1:9876. The application state is kept in .celld/dev, so a later invocation uses the same durable data.
celld devUse celld dev --port PORT to select a different Worker port, and celld dev --host IP to select a different interface. A non-loopback IP exposes the Worker listener to the network, while the internal operator listener stays on loopback. The command watches the project and rebuilds after a source or configuration change; it keeps the current application running if a build fails, and a successful restart retains durable state. The default display highlights the application URL and hides the node warning and information logs, so use celld dev --logs when you want them. Errors stay visible without that flag.
One trap is documented. State survives a configuration change and celld does not migrate it, so an object can keep a value that the new configuration rejects, and the failure then looks unrelated to the change. celld dev --clean deletes .celld/dev before the server starts.
celld dev --cleanFor a container run, the release image contains the celld binary and is published for Linux x86-64 and ARM64. The README gives this check first:
docker run --rm ghcr.io/denoland/celld --versionPersisting local state and passing standard AWS credential variables through looks like this in the README, with the bucket and endpoint replaced by your own:
docker volume create celld-state
docker run --rm --network host \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
-e AWS_SESSION_TOKEN \
-e CELLD_WATCH=/var/lib/celld/state \
-v celld-state:/var/lib/celld \
ghcr.io/denoland/celld \
--bucket s3://my-cells-bucket \
--endpoint https://ACCOUNT.r2.cloudflarestorage.com \
--region auto \
--listen 0.0.0.0:8080 \
--internal-listen 10.0.0.12:8081 \
--advertise node-a.internal:8081Drop --endpoint and --region for AWS S3. Expose port 8080 through the load balancer and keep port 8081 on the private network. The examples directory has a project per service, from hello and counter through kv, queues, d1, r2, workflow, and cron, so the shortest path to a working deployment is to start from one of those rather than from a blank directory.
The single-node write penalty and the bucket dependency
The most concrete limitation is the one the README volunteers: on a single node every write waits for the bucket, and those writes are much slower. The fast path exists only when the owner can hand the LTX data to one or two peers and consider the write durable once they hold it on disk. So a one-machine deployment gets the programming model but not the latency profile, and the documentation does not offer a way around that other than running more nodes.
The second constraint is the bucket itself. Ownership is decided by a conditional write, and the bucket holds deployments, cell state, and ownership records. That makes the object store a hard dependency rather than a backup target. The README lists S3-compatible storage, Google Cloud Storage, and Azure Blob Storage, and mentions that celld uses the standard AWS credential chain, including Pod Identity credentials on Amazon EKS. It does not describe what happens when the bucket is unreachable for an extended period, and it does not document rollback of a deployment.
The third is configuration drift. Because state survives a configuration change and celld does not migrate it, an object can hold a value the new configuration rejects. The symptom appears somewhere else entirely. celld dev --clean is the documented escape hatch for local work, but the README does not describe an equivalent migration story for a production fleet, so plan configuration changes as deliberate events rather than casual edits.
celld compared with running Workers on Cloudflare
The obvious alternative is Cloudflare itself. The difference in approach is not the API surface, which celld deliberately copies, but where coordination and durability live. On Cloudflare, the network and the platform's own storage layer handle placement, failover, and durability, and you do not run nodes or own a bucket. With celld, you run one process per machine, the nodes that share a bucket form the fleet, and a conditional bucket write decides which node owns a cell. There is no consensus service in the loop.
That swap buys self-hosting and a state store you control, and it costs you operational work: nodes to run, a bucket to keep reachable, peer networking on a private interface, and the single-node write penalty until you scale to at least two nodes. The README also draws a scope line that Cloudflare does not, stating that anything needing the Cloudflare network, a GPU, or a browser farm is out of scope, with the compatibility page listing the gaps.
A different kind of alternative is a general-purpose durable execution or actor framework where you write against a new API and manage storage yourself. celld's distinguishing choice is that it does not ask you to rewrite: it deploys from the wrangler.json you already have and runs the Wrangler bundles through an embedded V8. If you are willing to change your application code, the comparison changes entirely; if you are not, celld is aimed squarely at you.
Licence, upgrades, and what maintenance costs
celld is licensed under Apache-2.0, and the repository also carries a LICENSE.tokio file, which is worth reading if you redistribute the binary, since the project embeds a Rust runtime. This is a description of what the repository contains, not legal advice; if you ship celld inside a product, have someone qualified read both files.
Upgrade cost is low by design. The installer keeps each release under ~/.local/lib/celld/releases and points one symlink at the current one, so switching versions is a symlink change, and removing celld is deleting the symlink and the releases directory. Releases are frequent: v0.4.1 on 2026-09-05, v0.5.0 on 2026-09-15, and v0.5.1 on 2026-09-19, with the last push to the repository on 2026-09-19. Version numbers below 1.0 across that cadence mean you should read the release notes before moving a production fleet, and the README does not document a rollback procedure for a deployment or a downgrade path for cell state.
The operator-facing surface is small, which keeps the ongoing cost down: one process per machine, a bucket, and two listeners, one public and one internal. The work that does not go away is watching the bucket, because ownership, deployments, and cell state all live there, and keeping peer networking on the private interface rather than exposed alongside port 8080.
Editorial conclusion
Adopt celld if you already have a wrangler.json and want Durable Objects, KV, Queues, D1 or Workflows running on hardware you control, with state in an S3-compatible bucket you already pay for. Do not adopt it if your application depends on the Cloudflare network, a GPU, or a browser farm; the README states those are out of scope, and the compatibility page lists the remaining gaps. Before committing, run celld dev against one of the examples, then verify the guarantees document against your durability requirement and confirm that your bucket provider supports the conditional writes the ownership model depends on.
Frequently asked questions
Does celld need Docker or a cloud bucket to run locally?
No. celld dev starts one celld node with a local object store, so it needs no Docker and no cloud bucket, and it keeps application state in .celld/dev.
Why are writes slow on a single celld node?
A single node has no other node to send the LTX data to, so each write waits for the bucket upload, and the README states those writes are much slower. With two or more nodes the owner sends the data to one or two peers, and the write is durable as soon as they hold it on their disks.
Which Cloudflare services can run on celld?
celld runs Workers, Durable Objects, KV, Queues, D1, R2, Workflows, Cron Triggers, and static assets, plus experimental Containers and the Sandbox SDK. Anything that needs the Cloudflare network, a GPU, or a browser farm is out of scope, and the compatibility page lists each gap.
How does celld decide which node owns a Durable Object?
A conditional bucket write gives a node ownership of a cell, so exactly one node owns a cell at a time. The fleet needs no membership protocol, failure detector, or consensus service, and signed peer HTTP carries routing and replicated-log transport.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/denoland-celld)
Community notes