cloudflare/workerd: self-hosting the Cloudflare Workers runtime
The JavaScript / Wasm runtime that powers Cloudflare Workers
At a glance
- What is it?
- workerd is the open source JavaScript and WebAssembly server runtime behind Cloudflare Workers. It is aimed at engineers who want that execution model on their own machines, and its config format is where the real learning curve sits.
- Who is it for?
- workerd suits teams already writing Workers-style JavaScript who want the same runtime locally or on their own metal, and it suits anyone building a programmable HTTP proxy around fetch handlers. It does not suit anyone planning to execute untrusted third-party code on bare hosts, because the README states it is not a hardened sandbox and must run inside a virtual machine for that.
- 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 received new commits within the last day.
- What is it written in?
- Mainly C++, 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 workerd is for, and who it is not for
The README describes three uses: an application server for self-hosting code written for Cloudflare Workers, a development tool for testing that code locally, and a programmable HTTP proxy that intercepts, modifies and routes requests. All three share one property. The unit of deployment is a Worker, a script that receives fetch events, and the runtime is built on web platform standards such as fetch() rather than on a bespoke API.
The audience is narrower than the description suggests. workerd is server-first by design principle, explicitly not a CLI or a GUI tool. It targets people who already have Workers-shaped JavaScript and want it to run somewhere other than Cloudflare's network. If you are starting from a Node.js application and hoping for a drop-in replacement, the configuration model will feel foreign: there is no package.json entry point, no node_modules resolution by default, and no express-style middleware chain. The samples directory does include nodejs-compat variants, so a Node compatibility layer exists, but it is a compatibility surface, not the primary design.
Nanoservices, capability bindings and the config file
The architecture rests on two ideas the README states directly. The first is nanoservices: an application is split into components that are decoupled and independently deployable like microservices, but when one nanoservice calls another, the callee runs in the same thread and process. The second is capability bindings. Rather than exposing global namespaces, the config connects services to each other and to external resources through explicit bindings. The README's stated consequence is that the resulting code is more composable and immune to SSRF attacks, because a Worker cannot reach a host it was never handed a binding for.
Configuration lives in a Cap'n Proto text file. The README points at src/workerd/server/workerd.capnp as the complete reference, and says a library of sample config files sits in samples. A config declares a list of services and a list of sockets; a socket binds an address to a service, and a service names a worker. That is the whole data flow at the top level: bytes arrive on a socket, the socket routes to a service, the service runs a worker, and the worker's outbound calls only reach resources the config granted it.
Versioning follows the same logic. The README states that updating workerd never breaks your JavaScript, and that the version number is simply a date corresponding to the maximum compatibility date that version supports. You configure a worker to a past date and the runtime emulates the API as it existed then. This is a genuine design commitment, and it also means the compatibility date is a load-bearing field you should set deliberately rather than copy from an example.
Building workerd from source with Bazel
There is no documented package-manager install path in the README. The stated route is building from source with Bazel, and the toolchain requirements are specific. On Linux you need clang 22 or higher, libc++ 22 or higher, LLD 22 or higher, plus python3, python3-distutils and tcl8.6. On macOS the README lists an Xcode 16.3 installation and a Homebrew tcl-tk package. Windows is supported on x86-64 through install-deps.bat run from an administrator prompt, followed by a .bazelrc entry and bazel-env.bat in the shell.
The build command is short once the toolchain is in place:
bazel build //src/workerd/server:workerdFor a release build, the README gives this variant:
bazel build //src/workerd/server:workerd --config=releaseThe compiled binary lands at bazel-bin/src/workerd/server/workerd. If you installed dependencies after a first failed build, the README warns that cached toolchains may be stale and suggests resyncing them:
bazel fetch --configure --forceA first config is a Cap'n Proto file. The README's Hello World declares one service and one socket on port 8080, and embeds a JavaScript file:
using Workerd = import "/workerd/workerd.capnp";
const config :Workerd.Config = (
services = [
(name = "main", worker = .mainWorker),
],
sockets = [
( name = "http",
address = "*:8080",
http = (),
service = "main"
),
]
);
const mainWorker :Workerd.Worker = (
serviceWorkerScript = embed "hello.js",
compatibilityDate = "2023-02-28",
);The embedded hello.js registers a fetch listener and responds with a fixed body:
addEventListener("fetch", event => {
event.respondWith(new Response("Hello World"));
});You serve that config with the command the README gives, and a request to port 8080 should return the string from the script:
workerd serve my-config.capnpThe README points to the workerd.capnp comments and the samples directory for everything beyond this. It does not document a rollback procedure for a running service, and it does not describe a configuration migration path between versions, so treat those as open questions to answer from the schema itself.
The sandbox warning is the limitation that matters most
The README carries a warning in its own heading: workerd is not a hardened sandbox. It states that the runtime tries to isolate each Worker so it can only access configured resources, but that on its own it does not contain suitable defense-in-depth against implementation bugs. When running possibly-malicious code, the README says you must run workerd inside an appropriate secure sandbox such as a virtual machine, and notes that the Cloudflare Workers hosting service itself uses many additional layers. Bug reports that allow escape belong in Cloudflare's bug bounty program.
Read that as a boundary on the product, not a footnote. Capability bindings reduce the attack surface a Worker can reach by configuration, but they do not make the runtime a security boundary against a determined adversary who can trigger a memory-safety bug in C++ or V8. If your plan is multi-tenant execution of customer-supplied JavaScript, workerd is one layer of that stack and you still owe the virtual machine. If your plan is running your own code on your own hosts, the warning is largely irrelevant to you.
The second limitation is the build. Requiring clang 22 and libc++ 22 is a recent-toolchain constraint, and the README's own troubleshooting note about stale Bazel toolchains hints at how easily a first build goes sideways. Teams on older distributions will spend time on the toolchain before they spend any on their Worker.
Where workerd sits next to Deno and Node.js
The obvious comparison is Deno, because both are V8-based server runtimes with web-standard APIs and both ship a single binary. The difference is in the deployment model. Deno's unit is a module or a script you point the runtime at, and permissions are granted per process through flags. workerd's unit is a Worker declared in a Cap'n Proto config, and capabilities are granted per service through bindings in that file. Deno isolates by process permission; workerd isolates by configuration graph, and it is designed so that many nanoservices share one thread and one process rather than each getting its own.
Against Node.js the gap is wider. Node carries a large standard library and a package ecosystem resolved from disk. workerd is server-first and standards-based, with a Node compatibility layer available through the samples rather than as the core identity. If you need npm packages to resolve at runtime without a bundling step, Node remains the shorter path. If you want Workers code to run identically in development and in production, workerd is the runtime that makes that true, because it is the same code Cloudflare runs.
Licence, release cadence and upgrade cost
workerd is Apache-2.0. That permissive licence allows commercial use, modification and redistribution provided you keep the notices and state changes; it also includes a patent grant. It does not offer legal advice, and if you plan to redistribute workerd inside a product, the notice obligations are worth reading in the LICENSE file at the repository root rather than assuming.
The release cadence is visible in the tags: v1.20260921.1, v1.20260920.1 and v1.20260919.1, one per day, each dated. The last push to main was on 2026-09-21, so the repository is being pushed to currently. Daily tags do not mean you must upgrade daily. The README's backwards-compatibility claim is that a newer workerd never breaks your JavaScript, and that the version number is the maximum compatibility date supported. That reframes upgrades: the risk is not your Worker code breaking, it is that a config field or a binding type in workerd.capnp changes shape between versions. Verify against the schema that ships with the version you are moving to, and pin the compatibility date in your config rather than letting it float. The README does not document a downgrade path, so keep the previous binary until the new one has served traffic.
Editorial conclusion
workerd suits teams already writing Workers-style JavaScript who want the same runtime locally or on their own metal, and it suits anyone building a programmable HTTP proxy around fetch handlers. It does not suit anyone planning to execute untrusted third-party code on bare hosts, because the README states it is not a hardened sandbox and must run inside a virtual machine for that. Before adopting it, verify two things: that your toolchain meets the clang 22 and libc++ 22 minimums the build section lists, and that the compatibility date you intend to pin is supported by the workerd version you install. The config file is Cap'n Proto, not YAML or JSON, so read src/workerd/server/workerd.capnp before writing your first service.
Frequently asked questions
How do I install workerd?
The README documents building it from source with Bazel rather than a package-manager install. On Linux you need clang 22 or higher, libc++ 22 or higher and LLD 22 or higher, then run bazel build //src/workerd/server:workerd, which produces the binary at bazel-bin/src/workerd/server/workerd.
Is workerd a secure sandbox for running untrusted code?
No. The README states plainly that workerd is not a hardened sandbox and does not contain suitable defense-in-depth against implementation bugs. It says that to run possibly-malicious code you must place workerd inside an appropriate secure sandbox such as a virtual machine.
What config format does workerd use?
workerd is configured with a file in Cap'n Proto text format, usually named with a .capnp extension. The README points to the comments in src/workerd/server/workerd.capnp as the complete reference and to the samples directory for example configs.
Will upgrading workerd break my JavaScript?
The README states that updating workerd to a newer version will never break your JavaScript code. Each version number is a date corresponding to the maximum compatibility date it supports, and you can configure a worker to a past compatibility date so the runtime emulates the API as it existed then.
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/cloudflare-workerd)
Community notes