Ctrl K
ComunidadGuías…
✓ ficha editorial verificadaTypeScript

kody-celld - Self-hosted Kody Core on Deno celld

kody-celld es una reimplementación independiente de kody.codes diseñada para su propio hardware. Reproduce el mismo contrato MCP (search y execute, paquetes, secretos, jobs) que ofrece la versión en la nube de Cloudflare, pero como servicio local autocontenido. Se puede usar como un componente in-situ de un servicio existente, o como una capa integrada completa. El proyecto incluye documentación detallada sobre las limitaciones conocidas respecto a cada superficie.

kody-celld

A self-hosted Kody core that runs on Deno celld — the Cloudflare Workers + Durable Objects programming model on your own machines, with an S3-compatible bucket for durability.

It is a standalone reimplementation of kentcdodds/kody for your own hardware: the same MCP contract (search + execute, packages, secrets, jobs) plus every surface production Kody gets from Cloudflare, provided here as a self-hosted built-in, an adapter for a service you already run, or both — see the provision matrix for how each piece is provided.

Self-host it (Docker, five minutes)

What you need

RequirementNotes
Docker24+ with the docker compose plugin, on anything that runs Compose files: Synology Container Manager, QNAP Container Station, Unraid, Portainer, Docker Desktop, plain Linux.
CPU / archlinux/amd64 or linux/arm64 (Raspberry Pi 4/5 64-bit, Apple-silicon Docker Desktop). 1 core is enough; 32-bit ARM is not supported.
RAM~60 MB idle; ~600 MB measured with a dozen heavy execute runs in flight (each run is a V8 isolate). Plan 1 GB for Kody alone; the optional Ollama overlay wants several GB on top.
Disk~550 MB image + your data (SQLite files in one volume; a fresh install is 2 MB). Uploaded blobs live in the same volume.
NetworkOne TCP port (default 8080). Outbound HTTPS for execute code that calls APIs, npm imports and package installs. No Cloudflare account, no domain required for LAN/Tailscale use.
TimePulling the prebuilt image: about a minute. Building from source instead: ~2 min on a laptop, 10+ min on a small NAS.

Installation

1. Start it — save this as compose.yaml in a folder (or paste it into your NAS's Compose/"stack" UI) and run docker compose up -d:

services:
 kody:
  image: ghcr.io/kentcdodds/kody-celld:latest
  init: true
  restart: unless-stopped
  ports:
   - '8080:8080'
  environment:
   # The exact address you will type into the browser / give MCP clients.
   # Sign-in cookies and OAuth redirects are bound to it, so it must match:
   # trying it on this machine -> http://localhost:8080 (as below)
   # NAS / VPS on your LAN -> http://192.168.1.20:8080 or http://nas.local:8080
   # behind a reverse proxy -> https://kody.example.com
   KODY_PUBLIC_URL: http://localhost:8080
  volumes:
   - kody-data:/data
volumes:
 kody-data:

The commands below use http://localhost:8080; if you set a different KODY_PUBLIC_URL, use that instead (localhost only works from the machine running Docker).

Prefer to build from source (or want the AI / browser / mail / fleet overlays)?
git clone https://github.com/kentcdodds/kody-celld && cd kody-celld && docker compose up -d
uses the same image name and builds it locally when it is not present.

Check it and read your admin token

curl http://localhost:8080/health # {"ok":true, ...}
docker compose exec kody cat /data/kody.env # KODY_ADMIN_TOKEN + KODY_MASTER_KEY — back this up

Create your account

Open http://localhost:8080/ in a browser. The Set up Kody page asks for the admin token, your email and a password and signs you in to /account. (Created the first user with the admin API instead? /setup disappears once any user exists; sign in on /signin with the kc_… API token it returned, then set a password on /account.)

Connect an MCP client

claude mcp add --transport http kody http://localhost:8080/mcp

Cursor / VS Code / Claude Desktop: add an HTTP MCP server with that URL. Clients that only take a URL + header use a static token from /account/tokens:
Authorization: Bearer kc_…. Then ask it to "search Kody" — you should get the capability catalog back.

Where things live and how to keep them

TaskHow
DataEverything (users, packages, secrets, jobs, blobs, kody.env) is in the kody-data volume. Nothing is written elsewhere; deleting the container is safe, docker compose down -v is not.
Back updocker compose stop && docker run --rm -v <project>_kody-data:/data -v "$PWD":/backup alpine tar czf /backup/kody-data.tgz -C / data && docker compose start — restore by untarring into the volume.
Upgradedocker compose pull && docker compose up -d (from a source checkout: git pull && docker compose up -d --build). State and generated keys are kept.
TLSPut your reverse proxy (Synology Reverse Proxy, Nginx Proxy Manager, Caddy, Traefik) in front of 8080 with a certificate and set KODY_PUBLIC_URL=https://kody.example.com. Do not expose 8080.
Logsdocker compose logs -f kody
FailoverTwo or more nodes sharing an S3-compatible bucket (MinIO bundled): compose.fleet.yaml — see the guide.

The full walkthrough — NAS specifics, LAN/Tailscale vs. internet exposure, inviting people, approving secret hosts, every overlay (local AI, headless browser, SMTP, self-hosted npm CDN), the fleet path, and troubleshooting — is docs/getting-started.md.

Run locally (no Docker)

Prerequisites: Node ≥ 24.3, celld ≥ 0.6 on your PATH (install), and npm install (provides eesbuild, which celld uses to bundle the Worker).

npm install
npm run dev # celld dev . --port 8787 (state persists in .celld/dev)

In another terminal:

npm run smoke # mcp, packages, secrets, jobs, limits, memory, blobs, browser, webhooks, email, integrations, secret-providers, oauth-server, web, npm, install, community against http://127.0.0.1:8787
npm run smoke:cron # same, plus waits (~60s) for celld's real cron trigger to run a job

The dev config ships placeholder KODY_ADMIN_TOKEN / KODY_MASTER_KEY values in wrangler.jsonc. They only work against loopback: the Worker refuses to serve non-loopback requests while those placeholders are in effect. Put real values in a .dev.vars file (git-ignored) if you want to expose a dev node.

celld dev rebuilds on any project file change, including edits under smoke/; pass --watch-ignore 'smoke/**' if you edit tests while a smoke run is in flight.

Bootstrap a user and talk MCP

ADMIN=dev-admin-token
BASE=http://127.0.0.1:8787

# 1. create a user + API token (admin only)
curl -s -X POST $BASE/admin/users -H "authorization: Bearer $ADMIN" \
 -H 'content-type: application/json' -d '{"email":"you@example.com"}'
# -> { "user": {...}, "token": "kc_..." }

# 2. point any MCP client at $BASE/mcp. OAuth-capable hosts (Claude Code, Cursor,
# VS Code, …) need only the URL: they get sent to the built-in sign-in +
# consent page (docs/mcp-oauth.md). Others take `Authorization: Bearer kc_...`.
# Or call the tools by hand:
curl -s $BASE/mcp -H "authorization: Bearer kc_..." -H 'content-type: application/json' \
 -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search","arguments":{"query":"secrets"}}}'

execute runs an ES module with a default export, exactly like Kody:

import { kody } from 'kody:runtime'
export default async function main(params) {
 const me = await kody.whoami()
 return { me, sum: params.a + params.b }
}

search returns the capability catalog (kody.<capability>(args) inside execute), guides, and saved packages. Everything a client can do goes through those two tools; there is no other MCP surface.

Packages

A package is a file map with package.json, a non-empty README.md and AGENTS.md, JS modules, and optional kody.jobs. Save it with kody.packageSave({ files }) (or POST /api/call/packageSave), then either run an export (packageRun, host-side) or import it from any run:

import increment from 'kody:@kody-smoke/counter/increment'
export default async () => increment({ by: 2 })

Inside package code import { packageStorage } from 'kody:runtime' gives that package a private SQLite-backed Durable Object with get/set/delete/list/clear and raw sql(). Provenance is stamped at graph-build time, so a package's storage stays its own even when ad hoc code imports it. Ad hoc code has no scratch storage (same rule as Kody).

Two example packages live in examples/packages and are what the smoke suite installs. See docs/packages.md.

Secrets

import { kody } from 'kody:runtime'
await kody.secretSave({ name: 'github', value: '...' }) // value is never echoed back
await fetch('https://api.github.com/user', {
 headers: { authorization: 'Bearer {{secret:github}}' },
})

Placeholders are replaced only at the network boundary (the FetchGateway Worker Entrypoint that every isolate's outbound fetch is routed through), and only when:

  • the destination host is on the user's approved list (POST /admin/users/:id/secret-hosts),
  • the request is HTTPS (loopback hosts listed in KODY_ALLOW_INSECURE_SECRET_HOSTS excepted), and
  • every referenced secret exists.

Otherwise the request is denied before leaving the box and the run sees a 403/404 Response with a machine-readable reason. Sandbox code cannot approve hosts, read values, or reach /admin. Values are AES-GCM encrypted with a per-user HKDF key derived from KODY_MASTER_KEY; run history records secret names only. See docs/secrets.md.

Two more placeholder kinds ride the same gateway:

import { kody } from 'kody:runtime'
// OAuth: bring your own app, connect once in the browser, tokens refresh host-side
await kody.integrationSave({
 name: 'github',
 authorizeUrl,
 tokenUrl,
 clientId,
 clientSecret,
 scopes: ['repo'],
 allowedHosts: ['api.github.com'],
})
const { url } = aw...

Jobs

package.json#kody.jobs is validated on packageSave (src/packages/manifest.ts, src/jobs/schedule.ts) and reconciled into UserCell.jobs. celld's cron trigger calls scheduled() every minute → dispatchDueJobs iterates users, asks each UserCell to atomically claim due jobs (advancing next_run_at before running so a crash cannot double-fire), then executes the entry via executeRun with kind: 'job' and records a job_run.

Limits, quotas and audit

docs/operations.md

docs/ai.md

Blobs and browser rendering

docs/blobs.md

docs/browser.md

Run a fleet

Two or more nodes sharing an S3-compatible bucket (MinIO bundled): compose.fleet.yaml — see the guide.

Development

npm install
npm run dev # celld dev . --port 8787 (state persists in .celld/dev)

License

FSL-1.1-ALv2 (same as kody).

Relacionado en HostingByte

Alternativas y comparativas · Guías de HostingByte

Fuentes verificadas

Fuentes y verificación
Quality gate 100/100 · información trazable para SEO/GEO
Fuente 1 ↗Fuente 2 ↗Fuente 3 ↗Fuente 4 ↗Fuente 5 ↗Fuente 6 ↗Fuente 7 ↗Fuente 8 ↗Fuente 9 ↗Fuente 10 ↗
Citación para IA y agentes
Resumen citable, canonical y fuentes verificadas en texto plano.
llms.txt de esta ficha →