celld-operator

WorkerApp reference

The complete celld-operator.io/v1alpha1 WorkerApp API: every spec field with its default and validation, and the full status surface. One WorkerApp is one celld fleet serving one application deployment, so the resource, the fleet, and the app are 1:1:1.

Spec

Top-level fields

FieldTypeRequiredDefaultDescription
hostnames[]string (set)nounsetHostnames routed to this app on the shared Gateway; one route per hostname is reconciled in the app's namespace. Omit for internal-only apps.
appVersionstringyesunsetThe application deployment in the fleet bucket (written by celld deploy). Changing it triggers the gated rollout. The sentinel auto follows the bucket's deploy/current.json instead.
replicasint32 ≥ 1no3Fleet size when autoscaling is disabled; the initial size otherwise.
websocketsboolnofalseSelects the WebSocket ingress profile: session affinity, long idle timeouts, conservative scale-down.

celld

FieldTypeRequiredDefaultDescription
celld.imagestringyesunsetThe celld container image, tag included (e.g. ghcr.io/denoland/celld:v0.2.0). Mixed-version fleets are never created; changing this triggers the update strategy.
celld.updateStrategyRolling | RecreatenoRollingRollout path for celld version changes. Rolling is partition-stepped and restoring-gated; a Rolling request across a known-breaking celld boundary is refused. Recreate scales to zero first, which is an availability event by design.

bucket

FieldTypeRequiredDefaultDescription
bucket.namestring, ^(s3|gs)://.+yesunsetThe fleet bucket and prefix, e.g. s3://platform-cells/apps/chat or gs://…. The store must satisfy the fencing contract.
bucket.endpointstringnounsetS3-compatible endpoint URL when not AWS S3 (e.g. an R2 account endpoint). Rejected by celld for gs:// buckets.
bucket.regionstringnounsetStorage region, when it cannot be inferred (auto for R2).
bucket.credentialsFrom.iamRolestringnounsetIAM role ARN assumed via the pod's service account (IRSA / Workload Identity), or the literal auto to have the operator provision a prefix-scoped role. auto provisioning is not implemented yet, so the fleet reports BucketCredentialsReady: False and you annotate the fleet ServiceAccount yourself.
bucket.credentialsFrom.secretRefstringnounsetA Secret with static credentials (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY), for stores without role-based auth. Exactly one mechanism applies; prefer iamRole.

resources

FieldTypeRequiredDefaultDescription
resources.memoryGiint32 ≥ 1no8Container memory limit per pod, in GiB. The operator derives CELLD_MAX_RSS_MB (~80% of the limit, set explicitly because the upstream default is not cgroup-aware).
resources.maxResidentCellsint32 ≥ 1no1000Hard per-node resident-cell admission limit (CELLD_MAX_RESIDENT_CELLS). Upstream sizing: ~1,000 cells per 8 GiB.

vars and service

FieldTypeRequiredDefaultDescription
vars.secretRefstringyes, if vars setunsetSecret whose vars.env data (NAME=value lines) is mounted and passed via CELLD_VARS_FILE. Rotation rolls the fleet through an ordinary gated rollout.
service.typeClusterIP | LoadBalancer | NodePortnoClusterIPShape of the serving Service fronting the Worker listener. ClusterIP suits ingress backends and in-cluster consumers (<app>-celld.<ns>.svc:8080); LoadBalancer provisions a cloud LB; NodePort suits bare-metal edges.
service.annotationsmapnounsetMerged onto the serving Service: internal-LB flags, protocol hints, health-check tuning.

autoscaling

FieldTypeRequiredDefaultDescription
autoscaling.enabledboolnofalseMaterializes a KEDA ScaledObject over the operator's /state-derived metrics; paused during rollouts.
autoscaling.minReplicasint32 ≥ 1no2Scale floor; keep ≥ 2 for HA. Also the PodDisruptionBudget floor.
autoscaling.maxReplicasint32 ≥ 1no10The tenant's cost ceiling.
autoscaling.targets.residentCellUtilizationint32 1-100no70Target fleet-average percentage of occupied vs maxResidentCells. Conservative by default, since celld has no rebalancer. Any shedding pod triggers scale-up regardless.
autoscaling.targets.p95LatencyMsint32 ≥ 1nounsetOptional gateway-side latency target so traffic-bound, stateless-heavy apps scale even at low cell counts. Unset disables the latency signal.

telemetry

FieldTypeRequiredDefaultDescription
telemetry.enabledboolnotrueEnables celld tracing (CELLD_OTEL=1).
telemetry.sinkbucket | otlpnobucketDestination. Unset means bucket, unless otlpEndpoint is set, in which case otlp is inferred.
telemetry.otlpEndpointstringnounsetCollector base URL for the otlp sink (OTEL_EXPORTER_OTLP_ENDPOINT).
telemetry.retentionstringno30dCELLD_OTEL_RETENTION for the bucket sink, or none to defer to bucket lifecycle rules. Ignored by the otlp sink.

Status

Phase

PhaseMeaning
PendingInitial reconciliation in progress.
ReadyFleet converged: all pods ready, restoring=0, rolled-out version matches.
RollingOutThe gated rollout is stepping through ordinals.
RecreatingA Recreate upgrade is in progress (scale to zero, drain, restart).
DegradedA refusal or stuck gate; the message names it (e.g. a breaking upgrade without Recreate, or a rollout wait that exceeded its budget).

Fields

FieldDescription
status.rolledOutAppVersionThe appVersion every fleet pod is serving; trails spec.appVersion while a rollout is in flight. In auto mode, the concrete version being served.
status.rollout.partitionThe StatefulSet rolling-update partition owned by the rollout controller. 0 means no rollout in progress.
status.rollout.waitingOnThe gate the rollout is blocked on, e.g. celld-2: Ready or fleet: restoring=3. Empty when not waiting.
status.fleet.readyPods passing the celld health check.
status.fleet.restoringFleet-wide sum of cold routes holding or awaiting an activation permit. Rollouts step only at 0.

Conditions

ConditionMeaning when not True
Available / Progressing / DegradedStandard phase reflection; the Degraded message names the refusal.
BucketCredentialsReadyiamRole: auto provisioning is not implemented yet, so annotate the fleet ServiceAccount yourself.
IngressReadyGateway API CRDs missing, or a route error; hostnames are not routed.
MeshPolicyReadyIstio absent; NetworkPolicy alone guards :8081.
AutoscalingReadyKEDA absent; spec.autoscaling has no effect.
DeployTrackingReadyVersionMismatch: the bucket's deploy/current.json and spec.appVersion disagree (pinned mode), or tracking reads fail.

Printer columns

$ kubectl get workerapps -n tenant-acme
NAME   PHASE   APP          READY   RESTORING   AGE
chat   Ready   a3f9c1d2e4b57081   3       0           2m

Complete example

apiVersion: celld-operator.io/v1alpha1
kind: WorkerApp
metadata:
  name: chat
  namespace: tenant-acme
spec:
  hostnames: ["chat.acme.example.com"]
  appVersion: a3f9c1d2e4b57081
  celld:
    image: ghcr.io/denoland/celld:v0.2.0
    updateStrategy: Rolling
  replicas: 3
  bucket:
    name: s3://platform-cells/apps/chat
    endpoint: https://ACCOUNT.r2.cloudflarestorage.com
    region: auto
    credentialsFrom:
      iamRole: arn:aws:iam::123456789012:role/celld-chat
  resources:
    memoryGi: 8
    maxResidentCells: 1000
  vars:
    secretRef: chat-vars
  service:
    type: ClusterIP
  websockets: true
  autoscaling:
    enabled: true
    minReplicas: 3
    maxReplicas: 10
    targets:
      residentCellUtilization: 70
      p95LatencyMs: 250
  telemetry:
    enabled: true
    retention: 30d