Configuration
The root secret
The scheduler needs one secret, BEACHBALL_SECRET: at least 32 characters of
random data, for example openssl rand -base64 32. Everything else derives
from it with HMAC-SHA-256:
| Derived value | Used for |
|---|---|
| Internal object key | Authenticates requests between Durable Objects |
| Credential issuer key | Issues host credentials at enrollment |
| Connection-signing key | Ed25519 key that signs private-network connection grants |
| Bootstrap credential | Administrator and default client when no identities are configured |
| Default ULA prefix | RFC 4193 /48 for private networks when none is configured |
celld 0.5 and later have no secret store, so the secret and the manifest
travel as Wrangler vars in the deployed config:
node scripts/scheduler-deploy-config.mjs --secret-file FILE [--manifest FILE]
writes scheduler/.deploy.wrangler.json for celld deploy; delete it
afterwards. The value is then stored in the deployment record in the fleet
bucket, whose credentials already grant control of the whole fleet. pnpm dev
does this for you.
Rotating the root secret rotates all of them: hosts must join again and
clients must fetch the new connection verification key. The public
verification keys are published at GET /v1/.well-known/beachball.
The deployment manifest
DEPLOYMENT_MANIFEST is optional JSON. Every key is optional; unknown keys
are rejected so that a typo fails loudly. An empty manifest ({}) and no
manifest are the same deployment.
| Key | Default | Notes |
|---|---|---|
region | "default" | Hosts join this region. |
deploymentId | "beachball" | |
releaseVersion | "development" | Included in exported alerts. |
publicApiOrigin | none | When set, readiness also probes this origin's celld health. |
admissionsEnabled | true | |
bootstrapCredential | true when no controllers and no operators are configured | |
controllers | [] | Client identities; see below. |
operators | [] | Operator identities; see below. |
workloadClasses | one default class | See below. |
hostEnrollment.autoActivate | true | false keeps new hosts draining until bb host activate. |
images.manifestBaseUrl | none | Where hosts fetch signed image manifests; hosts can also set it. |
images.trustedPublicKeys | [] | Image-signing keys hosts trust. |
images.allowOci | true | Accept container image references (ubuntu:26.04) that hosts build themselves; false accepts only signed images. |
images.registryUrl | none | The image registry (ADR 0008); classes with registryImages ask it who may boot an image. |
networking.ulaPrefix | derived | |
networking.dnsServers | [] | Private IPv6 resolvers. |
networking.additionalVerificationKeys | [] | Retired signing keys clients should still accept. |
networking.relay | no relays, not required | Packet relay fallback. |
limits.* | 10 000 allocations, 1 000 hosts, 100 requests/s, 64 KiB payloads | |
retention.* | 30 days (hosts: 90) | |
health.healthyHeartbeatSeconds / lostHeartbeatSeconds | 30 / 90 | |
metrics | false | Per-request API metrics in the operations object. |
auditSink | none | Without a sink, audit records stay in the scheduler. |
alertDestinations | [] |
Identities
A controller (client) needs only clientId and the SHA-256 of its token:
{ "controllers": [{ "clientId": "ci", "sha256": "…" }] }tenantId defaults to the clientId, allowedWorkloadClasses to ["*"],
and the credential never expires unless expiresAt is set. Operators need
operatorId and sha256; role defaults to administrator.
Workload classes
A class needs only a name. Omitted fields take the default class values:
| Field | Default |
|---|---|
cpuMillis / memoryMiB / ephemeralDiskMiB | 1000 / 1024 / 4096 |
maxCpuMillis / maxMemoryMiB / maxEphemeralDiskMiB | the class values; the built-in default class allows up to 64 000 / 256 GiB / 1 TiB |
imageRef | none: requests name an image |
pinImage | false: requests may choose another signed image |
maxLifetimeSeconds / clientLeaseSeconds | 86 400 / 600 |
allowedClients / allowedNetworks | ["*"] |
privateIngress | true |
registryImages | false: an image other than imageRef needs no registry check |
platformEndpoints | []: host services reachable at the guest gateway, such as registry (ADR 0008) |
A request may set image, cpuMillis, memoryMiB, and ephemeralDiskMiB
up to the class maximums. An image is either a signed Beachball image
(name@sha256:<digest>, a single-segment name) or a container image
reference, which a host that joined with --oci-kernel pulls and builds with
its own signing key. Hosts boot only images signed by a key they trust; set
pinImage: true to restrict a class to its imageRef, or images.allowOci: false to refuse container references. A class with registryImages: true
also boots images the image registry converted, but only those it says
belong to the caller's tenant.
Production
A production deployment typically adds explicit controllers and
operators (which turns the bootstrap credential off), a stable
networking.ulaPrefix, images, an auditSink, and alertDestinations.
deploy/production-manifest.example.json
shows every section.