Skip to main content
AI & Technology

Want Workers APIs on Your Own Servers? Meet Deno celld and Its Trade-offs

Deno’s open-source celld runs applications built with Cloudflare Workers APIs on infrastructure you operate. Its deployment manifests and runtime bindings are interesting, but compatibility and security boundaries matter.

DenoCloudflare WorkerscelldSoftware ArchitectureDeployment
AI-generated illustration of an administrator reviewing a deployment in a fictional server room; it does not depict actual Enersys or customer personnel, systems or facilities
AI-generated illustration of a deployment review in a fictional server room. It does not depict actual Enersys or customer personnel, systems or facilities.

A new application release does not always have to mean stopping every node and starting its process again. Deno’s open-source project celld offers one model for applications built with Cloudflare Workers APIs: publish a deployment manifest to object storage, then let running nodes adopt the new version in place.

The names matter. celld is a Deno project for self-hosting, not a Cloudflare product. It is not an official Cloudflare portability guarantee, either. Its goal is to run a defined set of Workers-style runtime and services on machines you operate. The celld project and documentation list Workers, Durable Objects, KV, Queues, D1, R2, Workflows, Cron Triggers and static assets within stated boundaries.

What does deploying without a restart mean?

In celld, one process on one machine is a node. Nodes that use the same bucket form a fleet. An application is deployed as a bundle and manifest to supported object storage, such as S3-compatible storage, Google Cloud Storage or Azure Blob Storage.

After a new version is deployed, each node checks deploy/current.json at a configured interval, 30 seconds by default in the documentation, and adopts the new deployment without restarting its process. A request that began on the old code continues on that version. An active Durable Object moves to the new version when it has no work in progress, or when the configured maximum age forces a move. The deployment and version-transition behavior

“Without a restart” therefore means the node process does not have to be restarted to receive an application deployment. It does not mean every request switches versions at once or that two versions never run together. During rollout, a Worker on one version may call a Durable Object on another. Code and messages should remain compatible with the previous version during that transition.

If a cell stays active beyond CELLD_DEPLOY_MAX_AGE_S (60 seconds by default in the documentation), celld can force the move, cancel the running work and close regular WebSockets with code 1012. Hibernatable WebSockets follow a different path. Teams should test long-running requests and reconnect behavior instead of checking only whether the node process stays up.

This model may suit teams that release often and do not want every application deployment tied to a fleet restart. Upgrading the celld runtime itself is a separate operation, with version-specific requirements. Changing the runtime binary is not the same as deploying an application bundle.

Bindings pass runtime resources to the app, but they are not a DI container

Cloudflare Workers exposes configured resources through env, including D1 databases, queues and object storage. Cloudflare calls these bindings: a way for a Worker to access resources and APIs provided by the runtime. Cloudflare Workers bindings

From an application-design perspective, the runtime supplies dependencies. But bindings are not a full dependency-injection container. They do not assemble an application object graph or resolve arbitrary dependencies. Developers still decide how services use the provided resources.

An Order API could keep business logic separate from runtime SDKs by defining its own interfaces:

interface OrderStore {
  save(order: Order): Promise<void>;
}

interface ReviewJobs {
  publish(orderId: string): Promise<void>;
}

function createOrderService(deps: {
  orders: OrderStore;
  reviewJobs: ReviewJobs;
}) {
  return {
    async submit(order: Order) {
      await deps.orders.save(order);
      await deps.reviewJobs.publish(order.id);
    },
  };
}

A Worker handler can construct D1OrderStore(env.ORDERS_DB) and QueueReviewJobs(env.REVIEW_JOBS), then pass them to createOrderService(). A unit test can pass fake stores and queues instead. The separation is specific: the business service knows its own work interfaces, not whether persistence uses D1 or which SDK sends the message. The adapters and binding configuration still need separate implementation and tests.

Moving to another runtime requires adapters for its resources and tests for each service’s actual behavior, such as transactions, retries, ordering, timeouts and errors. A binding with the same name does not guarantee identical semantics.

These boundaries can make team ownership clearer: application engineers own business rules and interfaces, platform engineers own adapters and runtime configuration, and infrastructure engineers own nodes, networking and storage. An adapter change then has a clear place to test, and adding another runtime does not require rewriting every business rule. That benefit comes from the team’s design, not from celld itself.

Cells and state: per-entity data needs an explicit storage design

celld uses the Durable Objects concept, calling an instance a cell. Each cell has a name and its own SQLite database. This can fit work with clear boundaries, such as a chat room, document, user or AI agent. Each instance handles its own state through events; its SQLite database is not shared with other cells. Cells and lifecycle

Do not confuse a cell’s state with files in R2. SQLite and recovery data follow celld’s durability mechanism, with the bucket holding long-term data. The R2 binding addresses object storage directly and has its own semantics. Choosing what belongs in a cell, D1 or object storage remains an application data-design decision; “serverless” does not make it automatically.

This design also affects latency. The documentation says a single node waits for a bucket write to prove durability. In a multi-node fleet, a write may be replicated to another node before it is uploaded to the bucket. A self-hosted setup should not be assumed to match an edge provider’s cost or speed. Test with the actual object-storage location, node count and write pattern. Durability details

Check the boundary before planning a migration

celld accepts a subset of Wrangler configuration and Workers APIs. If a configuration key or capability is unsupported, deployment is rejected. Start with the compatibility table for the services your application uses, then build, deploy and test each binding. Workers API compatibility does not mean full Cloudflare parity across every service or edge feature.

The security limits deserve equal attention. The current documentation identifies v0.6.2 as beta and says it is not safe for hostile multi-tenant use. One fleet runs one application, and the runtime trusts application code and fleet operators. Do not run code from mutually distrusting tenants in the same fleet. celld limitations and security boundary

celld does not terminate TLS. Both public and internal listeners use HTTP. Terminate TLS at a reverse proxy or in the application, and keep the internal listener and peer traffic on a private network or encrypted overlay. Never expose the internal port to the internet. HMAC protects some requests, but it does not encrypt peer traffic.

When is it worth a trial?

celld is worth studying when a team has a reason to self-host, wants a Workers-style programming model, has object storage and people ready to operate the fleet, network, TLS termination, observability, backups, recovery and upgrades. Even without adopting celld, separating business logic from runtime bindings through testable adapters is a useful design choice.

Build a cost model before deciding to move. Separate node compute, object storage and requests, network ingress and egress, redundancy, backups and the staff time needed to operate the runtime. A fleet dedicated to one application may make application-level cost comparison more direct. Per-tenant SaaS attribution still needs application telemetry and allocation rules; bindings do not produce accounting data, and celld does not provide isolation for tenants that do not trust one another. Compare measured workloads and operating effort instead of assuming self-hosting will cost less.

If the application depends on Cloudflare-only services or the edge network, needs isolation between tenants that do not trust one another, or lacks owners for nodes and private networking, do not start by moving production. Run a compatibility spike with test data first. Document the APIs in use, test cases, rollback approach and criteria for stopping the trial.

A small Order API is a practical starting point: read from D1 and enqueue a review job, then run the same tests in Cloudflare’s runtime and celld. That experiment will show more clearly than a broad claim of “portability” which business logic moves and which parts remain tied to infrastructure.

Sources: Deno celld on GitHub, celld documentation, celld limitations, celld security, Cloudflare Workers bindings

AI-generated illustration of an administrator reviewing a deployment in a server room. It does not depict actual Enersys or customer personnel, systems or facilities.

Talk with Enersys about software systems and infrastructure

"Empowering Innovation,
Transforming Futures."

Contact us to make your project a reality.