| title | Self-Hosting |
|---|---|
| description | Run the CellaFlow engine with Docker, configure TLS and authentication, and deploy to production. |
| icon | server |
The CellaFlow engine is distributed as a Docker image. This page covers running it locally for development and deploying it in production with TLS and authentication.
The fastest way to get started:
docker run -d \
--name cellaflow \
-p 50051:50051 \
-p 9090:9090 \
ghcr.io/cellaflow/cellaflow:latest| Port | Purpose |
|---|---|
50051 |
gRPC API (SDK connections) |
9090 |
Health check HTTP server |
Check health:
curl http://localhost:9090/health/ready
# {"status":"ready"}By default, RocksDB data is written inside the container and lost on restart. For persistent state, mount a volume:
docker run -d \
--name cellaflow \
-p 50051:50051 \
-p 9090:9090 \
-v $(pwd)/cellaflow-data:/data \
ghcr.io/cellaflow/cellaflow:latestFor local multi-service setups, use Docker Compose:
# docker-compose.yml
services:
cellaflow:
image: ghcr.io/cellaflow/cellaflow:latest
ports:
- "50051:50051"
- "9090:9090"
volumes:
- ./cellaflow-data:/data
environment:
- CELLAFLOW_AUTH_TOKEN=your-secret-key
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9090/health/ready"]
interval: 10s
timeout: 5s
retries: 5docker compose up -dSet a Bearer token to require authentication on all gRPC calls:
docker run -d \
--name cellaflow \
-p 50051:50051 \
-e CELLAFLOW_AUTH_TOKEN=your-secret-key \
ghcr.io/cellaflow/cellaflow:latestThe Python SDK reads CELLAFLOW_AUTH_TOKEN from your local environment automatically, or you can pass it explicitly:
# Set in environment
export CELLAFLOW_AUTH_TOKEN=your-secret-key
# Or pass to workflow (advanced)
@workflow(version="1.0.0", target="engine:50051", secure=True)
def my_workflow(input: str) -> str: ...The engine accepts tokens via both Authorization: Bearer <token> and x-api-key: <token> headers.
Provide a certificate and private key for TLS:
docker run -d \
--name cellaflow \
-p 50051:50051 \
-e CELLAFLOW_TLS_CERT=/certs/cert.pem \
-e CELLAFLOW_TLS_KEY=/certs/key.pem \
-v $(pwd)/certs:/certs \
ghcr.io/cellaflow/cellaflow:latestConnect from the SDK with secure=True:
@workflow(version="1.0.0", target="engine.internal:50051", secure=True)
def my_workflow(input: str) -> str: ...Two of these have a different default in the container image than in the binary, because the image sets them itself. The distinction matters when running the binary directly.
| Variable | Description | Binary default | Image default |
|---|---|---|---|
CELLAFLOW_HOST |
gRPC bind address | [::1] — loopback only |
:: |
CELLAFLOW_PORT |
gRPC port | 50051 |
50051 |
CELLAFLOW_DB_PATH |
RocksDB data directory | ./cellaflow_data |
/data/cellaflow |
| Variable | Description | Default |
|---|---|---|
CELLAFLOW_AUTH_TOKEN |
API key for Bearer / x-api-key authentication |
None (auth disabled) |
CELLAFLOW_TLS_CERT |
Path to TLS certificate file (PEM) | None (cleartext) |
CELLAFLOW_TLS_KEY |
Path to TLS private key file (PEM) | None (cleartext) |
CELLAFLOW_MANAGEMENT_PORT |
HTTP health server port | 9090 |
CELLAFLOW_OTLP_ENDPOINT |
OTLP telemetry endpoint | http://localhost:4317 |
CELLAFLOW_LOG_LEVEL |
Log level | info |
CELLAFLOW_BACKEND |
Storage backend | rocksdb |
CELLAFLOW_SESSION_IDLE_TIMEOUT_SECS |
Idle seconds before a session evicts from memory | 1800 |
CELLAFLOW_ACTOR_SEND_TIMEOUT_MS |
Wait for room on a session's mailbox before shedding with RESOURCE_EXHAUSTED |
5000 |
CELLAFLOW_MAX_LEASE_LIFETIME_MS |
Ceiling on a single lease, independent of heartbeats | 3600000 (1 hour) |
CELLAFLOW_SYNC_WRITES |
fsync each durable write before acknowledging it | true |
Most variables also accept an unprefixed fallback (HOST, PORT, DB_PATH, AUTH_TOKEN,
API_KEY).
CELLAFLOW_MAX_LEASE_LIFETIME_MS is safe to raise and unsafe to lower. It exists because a
worker that hangs without dying heartbeats forever and would otherwise hold its lease — and its
graph position — indefinitely. But reclaiming a worker that is merely slow lets a second worker
repeat a side effect the first may already have performed. Set it above your slowest legitimate
tool call, not near it.
The engine exposes an HTTP health server on the management port (CELLAFLOW_MANAGEMENT_PORT,
default 9090):
| Endpoint | Status | Meaning |
|---|---|---|
GET /health/ready |
503 |
Engine is booting (boot recovery in progress) |
GET /health/ready |
200 {"status":"ready"} |
Engine is fully initialized and accepting gRPC connections |
CellaFlow emits OpenTelemetry traces and metrics to an OTLP collector. In local development, you can use the OpenTelemetry Collector or Jaeger:
# Run Jaeger locally
docker run -d \
--name jaeger \
-p 4317:4317 \
-p 16686:16686 \
jaegertracing/all-in-one:latest
# Then run the engine pointing to it
docker run -d \
--name cellaflow \
-p 50051:50051 \
-e CELLAFLOW_OTLP_ENDPOINT=http://jaeger:4317 \
ghcr.io/cellaflow/cellaflow:latestOpen Jaeger UI at http://localhost:16686 to inspect traces.