Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/denoland/celld/llms.txt

Use this file to discover all available pages before exploring further.

celld opens two HTTP listeners when it starts: the public Worker listener that handles incoming requests to your application, and the internal peer/operator listener that handles cell-ownership messages, operator API calls, and peer-to-peer coordination between nodes. For local development the built-in defaults are sufficient. A fleet node — one that sits behind a load balancer and communicates with peers — needs both listeners configured explicitly so that traffic is routed to the right place.

Local development

For a single local node you need only the three bucket settings. The public Worker listener defaults to 127.0.0.1:8080:
celld \
  --bucket "$CELLD_BUCKET" \
  --endpoint "$S3_ENDPOINT" \
  --region "$AWS_REGION"
You can set the same values through environment variables instead of flags:
export CELLD_BUCKET=s3://my-fleet-bucket
export S3_ENDPOINT=https://ACCOUNT_ID.r2.cloudflarestorage.com
export AWS_REGION=auto

celld
Once running, send a request to the Worker:
curl http://localhost:8080/
The internal listener defaults to 127.0.0.1:0 — a random loopback port chosen by the OS. That is fine for a single-node setup, but a fleet node must bind the internal listener to a stable address that peers can reach.

Fleet node

A fleet node must bind the public and internal listeners to separate, explicitly named addresses, and must advertise the internal address that other nodes can actually reach.
celld \
  --bucket "$CELLD_BUCKET" \
  --endpoint "$S3_ENDPOINT" \
  --region "$AWS_REGION" \
  --listen 0.0.0.0:8080 \
  --internal-listen 10.0.0.12:8081 \
  --advertise node-a.internal:8081
FlagEnv varPurpose
--listenCELLD_ADDRThe address the public Worker listener binds to. The load balancer or ingress proxy sends Worker traffic here.
--internal-listenCELLD_INTERNAL_ADDRThe address the internal listener binds to. Peers send ownership and coordination traffic here. The operator API is also on this listener.
--advertiseCELLD_ADVERTISEThe address (hostname or IP, plus port) that other nodes put in ownership records when they try to contact this node. Must be reachable by all peers.
Place the internal listener on a private network that you trust, such as a WireGuard or Tailscale overlay. celld does not terminate TLS on its own listeners, so the network itself is the trust boundary for peer traffic.
The --listen address is what your load balancer forwards public application traffic to. The --internal-listen / --advertise address is what other celld nodes use to hand off cell ownership and exchange coordination messages. Keep these two paths completely separate — do not expose the internal listener to the public internet.

Listener rules

celld enforces several rules at startup to prevent common misconfiguration:
  • Explicit non-loopback public listener requires an explicit internal listener. If you set --listen to a non-loopback address (anything other than 127.0.0.1 / ::1), you must also set --internal-listen. This rule catches an obsolete single-listener configuration from earlier releases.
  • Explicit advertised address requires an explicit internal listener. If you set --advertise, you must also set --internal-listen.
  • Literal public IPs in --advertise are rejected by default. If you need to advertise a public IP address, set CELLD_UNSAFE_PUBLIC_ADVERTISE=1. This setting does not resolve DNS names or restrict which address the internal listener binds to.
  • celld cannot verify hostname or port translation. You are responsible for ensuring that the address in --advertise routes to the --internal-listen port, not to the public Worker listener.

Health check

Every celld node exposes a health endpoint on the public listener:
GET /__celld/health
ConditionHTTP statusBody
Node is healthy200 OK{"ok":true}
Node is draining (shutting down)503 Service Unavailabledrain info
Configure your load balancer to poll this endpoint and remove a node from rotation when it returns 503. celld sets this status automatically when it receives SIGTERM or SIGINT, before it starts handing off cell ownership. See Shutdown & Rollout for the full drain sequence.

Docker

To run a celld node in a Docker container, create a named volume for SQLite files, then pass the network, bucket, and listener settings through environment variables and CLI flags:
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:8081
Drop --endpoint and --region for AWS S3. --network host gives the container direct access to the host network interfaces, which makes the --internal-listen IP address available without additional port-mapping configuration. If you use bridge networking instead, ensure that --advertise resolves to the address that other containers (or hosts) use to reach this container’s internal port.

Build docs developers (and LLMs) love