Skip to content

Deploy

By the end, OpenSwitchboard runs in production on Cloudflare: the bot as a container with a state Worker beside it, deployed from your terminal in one command.

You need: a Cloudflare account with a domain (a zone) in it, an API token for it in CLOUDFLARE_API_TOKEN (scopes: Set up accounts; Containers Edit is what the image copy needs), and the Slack tokens and provider key from Get started. No Docker, no clone.

The pieces

WorkerWhat it isNeeded?
memory (the state Worker)Durable Objects: config, run history, memory, schedules, overridesyes: without it nothing survives a restart
botThe always-on container: Slack, the model, the dispatcher, the dashboardsyes
residentOne always-warm container per onboarded repositoryoptional
sandboxA container per thread for toolsoptional

deploy all deploys them in that order.

Write the profile

bash
npx @coreplane/switchboard init --organization <org> --anthropic-key <key> --slack-app-token <xapp-token> --slack-bot-token <xoxb-token> --cloudflare <account id> --zone <zone>

You should see:

wrote to /Users/you/.switchboard:
  .env                  (mode 600)
  config/config.yaml
  deploy/profile.json
Worker configs from deploy/profile.json:
  written   .switchboard/deploy/cloudflare-memory/wrangler.jsonc
  written   .switchboard/deploy/cloudflare/wrangler.jsonc

The installation is ~/.switchboard (or SWITCHBOARD_HOME, or the directory you run in when it already holds one); every deploy command finds it from anywhere, and its .switchboard/ holds the rendered Worker configs. The profile's fields:

FieldMeaning
account, zoneYour account id; the zone every hostname is under.
workers.<name>script and hostname per Worker; delete one and the plan has no step for it.
imagesregistry (what init writes): the release's published images, copied into your account's registry by deploy all once per version, over HTTPS. build: each Dockerfile, built where deploy all runs (a checkout; needs Docker).
configSourceThe bot's config: a path, github://owner/repo/path@ref (needs CONFIG_REPO_TOKEN) or op://Vault/Item/field.
secretsSourceA directory of <NAME> files (~/.secrets/switchboard when absent) or op://Vault/Item.
access{ "teamDomain": "<team>.cloudflareaccess.com", "aud": "<AUD tag>" }: Cloudflare Access in front of the dashboards.

Point the config at the state Worker

In config/config.yaml:

yaml
runHistory:
  worker:
    baseUrl: https://switchboard-memory.<zone>
runtimeOverrides:
  worker:
    baseUrl: https://switchboard-memory.<zone>

Stage the secrets

deploy/secrets.manifest.json names every secret and its Workers. Values live at secretsSource, one file per name:

bash
mkdir -p -m 700 ~/.secrets/switchboard
openssl rand -hex 32 > ~/.secrets/switchboard/MEMORY_TOKEN

Add SLACK_BOT_TOKEN, SLACK_APP_TOKEN and ANTHROPIC_API_KEY the same way. Then:

bash
npx @coreplane/switchboard deploy secrets memory
npx @coreplane/switchboard deploy secrets bot
  • An absent required value refuses before any upload; an absent optional one is skipped, by name. Nothing is printed.
  • A shared bearer (MEMORY_TOKEN, SANDBOX_TOKEN, RESIDENT_*_TOKEN) carries one value on every Worker listed for it. --only NAME puts a subset.

Deploy

bash
npx @coreplane/switchboard deploy plan --only memory,bot
MEMORY_TOKEN="$(cat ~/.secrets/switchboard/MEMORY_TOKEN)" npx @coreplane/switchboard deploy all --only memory,bot

deploy plan executes nothing; its Images: line reads 0 of 1 present; deploy all copies the rest, or not probed (<why>) without a token. deploy all checks the account, copies the missing images into your registry, deploys the state Worker, pushes your config to it, deploys the bot, and waits until /healthz answers from the new container.

You should see:

copied into the account registry: bot …
[deploy:all] bot: live (commit <sha>; 45s after the upload)
deployed and live
FlagDoes
--only bot,resident · --skip sandboxSelect Workers.
--dry-runPrint the plan and what it would copy; copy and deploy nothing.
--affectedOnly the Workers whose inputs changed since the commit each one serves.
--forceBypass the bot and resident preflights.

Read from the environment: CLOUDFLARE_API_TOKEN (the registry read and the copy); MEMORY_TOKEN for the bot step; RESIDENT_READ_TOKEN for the resident; SANDBOX_TOKEN for the sandbox.

Add the optional Workers

WorkerConfig blockSecrets (shared with the bot)
residentexecution.resident.baseUrlRESIDENT_OPERATOR_TOKEN, RESIDENT_ADMIN_TOKEN; the GitHub App triple
sandboxexecution.type: cloudflare, execution.urlSANDBOX_TOKEN

From the package the profile already names them; from a checkout add the two workers entries. Mint the bearers, deploy secrets <worker> for each, then deploy all without --only. Repositories are onboarded from chat: @switchboard repo onboard acme/api.

Deploy from your CI

Optional: the same deploy from GitHub Actions. Commit deploy/profile.json and config/config.yaml to a repository of yours and add:

yaml
# .github/workflows/deploy-switchboard.yml
name: deploy switchboard
on:
  workflow_dispatch:
    inputs:
      targets:
        default: affected
permissions:
  contents: read
jobs:
  deploy:
    uses: <owner>/<repo>/.github/workflows/deploy-production.yml@v<version>
    permissions:
      contents: read
    with:
      cli: package
      version: <version>
      targets: ${{ inputs.targets || 'affected' }}
    secrets:
      CLOUDFLARE_DEPLOY_TOKEN: ${{ secrets.CLOUDFLARE_DEPLOY_TOKEN }}
      MEMORY_TOKEN: ${{ secrets.MEMORY_TOKEN }}
      RESIDENT_READ_TOKEN: ${{ secrets.RESIDENT_READ_TOKEN }}
      SANDBOX_TOKEN: ${{ secrets.SANDBOX_TOKEN }}

<owner>/<repo> is this repository; <version> is the workflow's tag and the CLI's version. Set the secrets with gh secret set <NAME>, then:

bash
gh workflow run deploy-switchboard.yml -f targets=all   # the first time
gh workflow run deploy-switchboard.yml                  # afterwards: only what is stale

Run the bot yourself

Without the Workers every optional capability is off. On any machine with Node and the installation init wrote:

bash
npx @coreplane/switchboard start

The same bot as a container:

bash
docker run -d --name switchboard --restart unless-stopped --env-file .env -v "$PWD/config:/app/config:ro" ghcr.io/coreplanelabs/switchboard:latest

Next