Skip to content

Latest commit

 

History

History
224 lines (172 loc) · 6.92 KB

self-hosting.mdx

File metadata and controls

224 lines (172 loc) · 6.92 KB
title Self-Hosting
description Run the CellaFlow engine with Docker, configure TLS and authentication, and deploy to production.
icon server

Self-Hosting

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.


Docker (Local Development)

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"}

Persistent Storage

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:latest

Docker Compose

For 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: 5
docker compose up -d

Authentication

Set 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:latest

The 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.


TLS

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:latest

Connect from the SDK with secure=True:

@workflow(version="1.0.0", target="engine.internal:50051", secure=True)
def my_workflow(input: str) -> str: ...

Environment Variables

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).

**Running the binary directly? `CELLAFLOW_HOST` defaults to `[::1]` — loopback only.** The engine will start, report healthy, and refuse connections from anywhere else, with no error to explain it. Set `CELLAFLOW_HOST=::` (or `0.0.0.0`) to accept external traffic. The Docker examples on this page do not pass it because the image already sets it; if you extract a command from one of them to run a locally built binary, you have to add it. **`CELLAFLOW_SYNC_WRITES` is the durability/latency dial.** Left at `true`, an acknowledged commit has reached stable storage and survives host power loss, not just a process or container failure. Set it to `false` and a commit is acknowledged once the write reaches the operating system's page cache — faster, and safe if your failure model is pods dying rather than hosts losing power. The cost of syncing is roughly 0.1–0.4 ms per commit on a local NVMe SSD and largely disappears under concurrent load, where RocksDB group-commits writers into a shared sync; on network-attached storage it is considerably higher. Measure on your own storage rather than assuming either way.

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.


Health Checks

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
Do not route gRPC traffic to the engine until `/health/ready` returns 200. The engine performs boot recovery (session reconstruction + timer respawning) before accepting connections — attempting to connect during recovery will fail.

Observability

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:latest

Open Jaeger UI at http://localhost:16686 to inspect traces.