Use complete stage JSONC files for non-secret settings and environment bindings for credentials. Do not rename internal packages, Worker bindings, database keys, or workflow formats to change the product brand.
Set S0_CONFIG_PROFILE to select a named profile. For example, company selects
config/company-dev.config.jsonc, config/company-pre.config.jsonc, and
config/company-prod.config.jsonc. A pre-<number> preview selects the same complete pre file.
Profiles do not merge with the default files. Keep a separate test file when the deployment needs
one.
The infrastructure loader, config:check, config:stage-url, and config:write-stage-vars use
the same profile selector. An explicit config:check -- --profile <name> takes precedence over
the environment selector.
Run nub run config:check and nub run test:config before requesting cloud credentials. The latter
uses the existing e2e runner against the real config compiler and CLI with owned local fixtures. It
does not start an app, allocate cloud resources, or replace the full Worker/browser suite.
An optional, complete brand object configures the browser name and assets:
Place local assets under apps/web/public. Asset URLs can also point to an operator-managed host.
The configured logo replaces the SolZero-specific primary loading animation. This keeps a custom
brand from requiring its own React components.
When brand is present, it takes precedence as a whole over VITE_S0_BRAND_*. When it is absent,
the existing public brand environment overrides and SolZero defaults remain supported. Infra resolves
the effective brand once, includes it in the configuration digest, and compiles browser scalars and
the server's S0_BRAND_NAME from that value. Worker requests do not read a deployment file.
The same name appears in OpenAPI/Scalar documentation, agent instructions, workflow-building copy, and Slack responses.
deployment.appName controls resource names and the health service identifier
<appName>-agent-control-plane. Changing it is a new resource identity, not a cosmetic rename.
integrations.githubApp.createPullRequestCommand optionally adds an executable alias such as
company-create-pr. Names must be lowercase letters/numbers separated by hyphens, with at most
64 characters. The canonical s0-create-pr executable remains available. Runtime skill packages use
the selected command and product name, while existing skill IDs and preference references remain
unchanged. Both the original c0 built-in ID and the SolZero built-in ID are recognized.
The helper requires an agent-container image built from this source. A deploy from your checkout
builds that image from packages/agent-container/Dockerfile, so a new alias reaches deployed
containers on the next deploy. A brand
change does not require a separate per-company image.
Configure auth capabilities, administrator identities, provider endpoints/defaults, GitHub App IDs,
Slack, MCP Context Forge, timeouts, and observability in the stage file. Keep every active secret as
an explicit { "env": "SECRET_NAME" } reference. A provider display name does not grant sign-in or
linking authority. Preserve encryption keys when importing encrypted records.
The existing LiteLLM default remains authoritative when both LiteLLM and Cloudflare AI Gateway are enabled. Adding the gateway must not silently replace the existing default model.
The API is served from the web origin at the fixed /api path; there is no separate API hostname.
Set zone and, optionally, webFqdn. The public API base is https://<webFqdn>/api, and Better
Auth remains at /api/auth/*. Arbitrary mount paths are not supported.
Cloudflare routes <webFqdn>/api and <webFqdn>/api/* directly to the API Worker. The web Worker
owns the custom domain and UI paths. The API Worker has no custom domain or workers.dev access.
Keep the hostname proxied in the configured zone, and review route ownership in the Alchemy plan.
Upgrading a deployment that used the former api.<webFqdn> hostname detaches that domain, so keep
the old stack running during a blue/green migration until external callers have moved.
Server-side web requests use the S0_API service binding, not the public origin. The combined stack
binds the API it creates; a separate web stack references S0Api in the same stage, so deploy the
API stack first. Local development and workers.dev previews forward /api through that binding.
They preserve caller headers, bodies, redirects, streaming and WebSocket responses. The API owns
session/API-key authentication and webhook signature checks; the web fallback imposes no browser
session requirement on API clients or webhooks. Internal MCP endpoints retain their existing guards.
Generated workflow and Slack URLs, agent callback bases and OpenAPI servers use the public API
base, never a service-binding origin. Workflow/webhook IDs do not change. Update external callers,
OAuth provider callbacks, GitHub App webhooks and Slack request URLs when moving public domains.
Auth callbacks stay under the web origin's /api/auth, without a doubled /api prefix.
nub run test:config includes owned local Worker transport tests. Run the full browser suite and review deployed routes, OAuth, signed webhooks,
streaming, WebSockets and observability before cutting over a production hostname.
The shared templates support master only. CI runs on GitHub-hosted ubuntu-latest runners.
Set repository variable S0_CI_RUNNER to another runner label to run the deployment and validation jobs there. Set repository variable
S0_CONFIG_PROFILE so static validation checks the fork's profile. In each GitHub environment
(pre or prod), set S0_CONFIG_PROFILE, DATABASE, APP_DB_MODE, and
PLANETSCALE_ORGANIZATION as needed. Empty DATABASE retains D1. PlanetScale deployments need
PLANETSCALE_SERVICE_TOKEN_ID and PLANETSCALE_SERVICE_TOKEN secrets.
Deploy Preview, PR Preview, and Deploy run only when the repository variable CLOUDFLARE_DEPLOY is the exact
value true. Set CLOUDFLARE_API_TOKEN for the stage you deploy. SolZeroAI/platform does not set
CLOUDFLARE_DEPLOY, so those jobs stay skipped and the public repository does not need the token.
A fork that deploys sets both the variable and the token. The same steps then run as before.
Put production deploy secrets in a GitHub environment named prod. Limit that environment to the
master branch. Only the Deploy job selects prod. Put preview secrets in the pre environment.
Keep Cloudflare account credentials in the matching environment so pre and prod can use separate
accounts without changing workflow source. Environment secrets override repository secrets of the
same name. Rotate GitHub App secrets in place as GH_APP_PRIVATE_KEY, GH_APP_CLIENT_SECRET, and
GH_APP_WEBHOOK_SECRET; the workflows compile them into the existing GITHUB_APP_* bindings.
The templates also pass the explicit Okta secret reference.
Ephemeral PR previews stay on D1. Standing pre and production can select PlanetScale. Do not enable paid per-PR databases until their provisioning, ownership receipts, and cleanup are supported. Release ownership guards remain in force.
deployment.planetscale is optional and applies only to remote DATABASE=planetscale stages:
"planetscale": {
"clusterSize": "PS_10",
"logicalDatabaseName": "s0",
"region": "aws-us-east-1",
"arch": "arm",
"replicas": 0,
"majorVersion": "17"
}Supply both cluster size and logical name when overriding. Without this object, the existing PS_10
size and s0 logical database remain unchanged. Optional fields otherwise retain provider defaults.
Replicas must be zero or at least two. Choose available SKUs and regions for the destination organization;
validation checks shape, not live availability. Review Alchemy's plan before changing existing databases:
region, architecture and replica changes can require replacement in the provider.
New account credentials do not transfer old resource identities. Plan separate source-read and destination-write access. Select the new zone and exact web/API hosts before changing deployment profiles, auth callbacks, GitHub webhook URLs, Slack callbacks, or external workflow webhooks.
Database copy tooling moves relational rows only. Inventory workflow/skill R2 objects, persistent KV, encrypted values, Durable Object conversation history, active Workflow executions, schedules, AI Search sources, and observability destinations separately. Preserve workflow IDs and webhook IDs. Do not assume that copying a session index row moves its Durable Object history or running work. See offline migration for transfer tooling and compatibility gates.
Keep the old stack available until data and functional checks pass. A rollback after new database writes needs explicit reconciliation; it is not just a previous Worker version.