On Azure
Deploy the app and gateway Workers on Cloudflare, and create each organization's Computer VM in Azure yourself
This page deploys the stack described in Start here. The gateway runs with COMPUTER_VMS set to self-managed: it creates no VMs. You create one Azure VM per organization, and the VM connects out to the gateway with that organization’s host token.
| The gateway | You |
|---|---|
Shows the Computer as Online while its VM is connected and Offline otherwise | Create, size, stop, start and delete the VM |
| Holds a Slack message that arrives while the VM is offline, says so once in the thread, and runs it when the VM connects | Start the VM again |
The examples use two hostnames on your Cloudflare zone:
| Placeholder | Serves |
|---|---|
app.example.com | The app Worker: dashboard, sign-in, Slack and GitHub callbacks |
gateway.example.com | The gateway Worker: sessions and the VMs’ WebSocket |
1. Get the code
git clone https://github.com/diggerhq/opencomputer.git
cd opencomputer
(cd services/managed-agents && npm ci)
(cd cloudflare-workers/api-edge && npm ci)
(cd web && npm ci)2. Create the Cloudflare resources
npx wrangler login
npx wrangler d1 create computer-app
npx wrangler d1 create computer-gateway
npx wrangler kv namespace create WIRE_KEYS
npx wrangler kv namespace create SESSIONS_KV
npx wrangler r2 bucket create managed-agents-session-logNote each ID; the next steps put them into the Workers’ configuration files.
3. Create the WorkOS, Slack and GitHub apps
The URLs below use https://app.example.com.
WorkOS. Create an application with redirect URI https://app.example.com/auth/callback, and set the sign-out redirect to https://app.example.com in the WorkOS dashboard. Keep the API key and client ID.
Slack. Create a Slack app with:
| Setting | Value |
|---|---|
| OAuth redirect URL | https://app.example.com/api/managed-agents/computer/slack/callback |
| Event request URL | https://app.example.com/api/managed-agents/computer/slack/events |
| Interactivity request URL | https://app.example.com/api/managed-agents/computer/slack/interactivity |
Bot token scopes:
app_mentions:read assistant:write channels:history channels:read channels:manage
groups:history groups:read groups:write im:history im:read im:write chat:write
files:read files:write reactions:write users:read users:read.emailBot events: app_mention, message.im, message.channels, message.groups, app_uninstalled, tokens_revoked, team_leave, user_change, and agent_session_stopped if Slack offers it. Keep the client ID, client secret and signing secret.
GitHub. Create a GitHub App with:
| Setting | Value |
|---|---|
| Callback URL | https://app.example.com/api/managed-agents/github/callback |
| Webhook URL | https://app.example.com/api/managed-agents/github/webhooks |
| Repository permissions | Metadata: read. Contents: read and write. Pull requests: read and write. Checks: write. |
| Events | Installation, Installation repositories, Repository, Push, Pull request |
Generate a private key. Keep the app ID, slug, client ID, client secret, webhook secret and private key.
4. Deploy the gateway Worker
Start from services/managed-agents/wrangler.production.jsonc and edit:
| Field | Value |
|---|---|
name, account_id, routes | Your Worker name, account, and gateway.example.com as a custom domain |
COMPUTER_VMS | Add it, with the value self-managed |
COMPUTER_PUBLIC_ORIGIN, OPENCOMPUTER_APP_URL | https://app.example.com |
SLACK_COMPUTER_CLIENT_ID | Add it, with the Slack client ID |
OC_GITHUB_APP_ID, OC_GITHUB_APP_SLUG, OC_GITHUB_APP_CLIENT_ID | From the GitHub App |
| D1, KV and R2 bindings | The computer-gateway database, WIRE_KEYS namespace and managed-agents-session-log bucket from step 2 |
services, tail_consumers | Remove both blocks |
COMPUTER_HOST_GATEWAY_URL, COMPUTER_DEFAULT_SIZE, COMPUTER_POOL_SIZE | Remove. They are not used. |
Write the secrets to a JSON file with mode 0600:
| Secret | Value |
|---|---|
OC_MANAGED_AGENTS_SECRET, OC_MANAGED_CRED_HMAC_SECRET, BLUE_USAGE_HMAC_SECRET, BLUE_SESSION_INDEX_ADMIN_SECRET | openssl rand -hex 32 each |
BLUE_CHANNEL_ENCRYPTION_KEY, COMPUTER_HOST_TOKEN_KEY | openssl rand -base64 32 each |
SLACK_COMPUTER_CLIENT_SECRET, SLACK_COMPUTER_SIGNING_SECRET | From the Slack app |
OC_GITHUB_APP_PRIVATE_KEY, OC_GITHUB_APP_CLIENT_SECRET, OC_GITHUB_APP_WEBHOOK_SECRET | From the GitHub App |
COMPUTER_ANTHROPIC_API_KEY | Your Anthropic API key |
Keep the value of COMPUTER_HOST_TOKEN_KEY at hand: step 7 derives each organization’s host token from it.
cd services/managed-agents
npx wrangler d1 migrations apply computer-gateway --remote --config wrangler.production.jsonc
npx wrangler deploy --config wrangler.production.jsonc --secrets-file gateway-secrets.json
curl https://gateway.example.com/health5. Deploy the app Worker
In cloudflare-workers/api-edge/wrangler.toml:
- Set
name,account_id, and the route toapp.example.com. - Set the D1 binding to
computer-appand the KV binding toSESSIONS_KV. - Set the
CREDIT_ACCOUNTbinding’sscript_nameto this Worker’s name. - Set
COMPUTER_ENABLED_FOR_ALL = "true"andMANAGED_AGENTS_API_URL = "https://gateway.example.com". - Remove
tail_consumers.
Secrets, in a second 0600 JSON file:
| Secret | Value |
|---|---|
WORKOS_API_KEY, WORKOS_CLIENT_ID | From WorkOS |
OC_MANAGED_AGENTS_SECRET, OC_MANAGED_CRED_HMAC_SECRET | The same values as the gateway |
AGENT_RUNTIME_USAGE_HMAC_SECRET | The gateway’s BLUE_USAGE_HMAC_SECRET |
SESSION_JWT_SECRET, CF_ADMIN_SECRET, SECRET_ENCRYPTION_KEY, EVENT_SECRET | openssl rand -hex 32 each |
Build the dashboard into the Worker’s assets, create the schema and deploy:
cd web && npm run build && cp -r dist/. ../cloudflare-workers/api-edge/assets/
cd ../cloudflare-workers/api-edge
npx wrangler d1 execute computer-app --remote -c wrangler.toml --file schema-snapshots/current_schema.sql
npx wrangler d1 migrations apply computer-app --remote -c wrangler.toml
npx wrangler deploy -c wrangler.toml --secrets-file app-secrets.json
curl https://app.example.com/health6. Build and upload the Computer release
From the repository root:
computer/deploy/bundle.sh /tmp/calvin.bundle
az login
az account set --subscription '<SUBSCRIPTION>'
export REGION=eastus
export RG=computers
export STORAGE_ACCOUNT=<globally unique, lowercase>
az group create -n "$RG" -l "$REGION"
az storage account create -n "$STORAGE_ACCOUNT" -g "$RG" -l "$REGION" \
--sku Standard_LRS --allow-blob-public-access false
az storage container create --account-name "$STORAGE_ACCOUNT" -n bootstrap --auth-mode key
az storage blob upload --account-name "$STORAGE_ACCOUNT" -c bootstrap \
-n calvin.bundle -f /tmp/calvin.bundle --auth-mode key --overwriteThe bundle holds the Computer source and the model proxy built for Linux amd64. The container stays private; step 8 gives the VM a URL that is valid for one hour.
7. Get the organization ID and make the host token
- Open
https://app.example.comand sign in. - Open Settings and copy the Organization ID.
- Derive the organization’s host token from
COMPUTER_HOST_TOKEN_KEY:
export ORG_ID='<ORGANIZATION_ID>'
export COMPUTER_HOST_TOKEN_KEY='<the gateway secret>'
export TOKEN="sph_$(printf 'calvin-host:%s' "$ORG_ID" \
| openssl dgst -sha256 -hmac "$COMPUTER_HOST_TOKEN_KEY" -binary \
| base64 | tr '+/' '-_' | tr -d '=')"The token is valid for that organization only, and for as long as the key does not change.
8. Create the VM
The VM is Ubuntu 24.04 on amd64 with a second, blank disk for /data. It needs outbound internet access and no inbound access.
Make a download URL for the release, then write the VM’s first-boot script with self-managed-user-data.sh. The script it prints stores the gateway URL and the token in a root-only file on the VM, downloads the release and runs its boot script:
EXPIRY="$(date -u -d '+1 hour' +%Y-%m-%dT%H:%MZ 2>/dev/null || date -u -v+1H +%Y-%m-%dT%H:%MZ)"
export BUNDLE_URL="$(az storage blob generate-sas --account-name "$STORAGE_ACCOUNT" \
-c bootstrap -n calvin.bundle --permissions r --expiry "$EXPIRY" \
--https-only --full-uri --auth-mode key -o tsv)"
(umask 077; GATEWAY_HOST=gateway.example.com \
computer/deploy/vm/self-managed-user-data.sh > computer-user-data.sh)Create the VM. az vm create also creates a virtual network, a public IP address for outbound traffic, and a network security group; --nsg-rule NONE leaves that group without a rule that allows inbound traffic.
az vm create -g "$RG" -n computer-1 -l "$REGION" \
--image Canonical:ubuntu-24_04-lts:server:latest \
--size Standard_D2as_v5 \
--os-disk-size-gb 64 --data-disk-sizes-gb 128 \
--nsg-rule NONE \
--admin-username azureuser --generate-ssh-keys \
--custom-data computer-user-data.sh \
--tags organization="$ORG_ID"Any amd64 size with 2 vCPUs and 8 GiB of memory or more works; az vm list-skus -l "$REGION" --size Standard_D2 -o table lists the sizes your subscription can use.
The first boot installs the toolchains, formats the blank disk, mounts it at /data and starts the host. It takes about six minutes. The boot script formats a disk only when it is blank.
The token stays in the VM’s custom data. On the VM only root can read it; Azure does not return custom data once the VM exists.
9. Add the Slack app and connect GitHub
- Open
https://app.example.com. The Computer showsOnlineonce the VM has connected. - Click Add to Slack and approve.
- Connect GitHub and choose the repositories.
- Mention
@computerin a channel.
Repeat steps 7 and 8 for each further organization, with another VM name.
Stop, start and resize
Stop the VM with az vm deallocate; the Computer shows Offline and Slack messages wait. Start it with az vm start; the host reconnects and the workspace is still on /data. To resize, run az vm resize with the new size.
az vm delete does not delete the data disk.
Check a VM
No rule allows SSH. The boot log is /var/log/calvin-boot.log, and the two services are slack-presence-host and oc-modelproxy. Run checks with Azure Run Command:
az vm run-command invoke -g "$RG" -n computer-1 --command-id RunShellScript --scripts \
"systemctl is-active slack-presence-host.service oc-modelproxy.service; tail -50 /var/log/calvin-boot.log"Update a VM
A self-managed VM does not update itself. Build and upload a new bundle (step 6), make a new BUNDLE_URL (step 8), and run:
az vm run-command invoke -g "$RG" -n computer-1 --command-id RunShellScript --scripts "
set -e
curl -fsSL --retry 5 -o /opt/bootstrap/calvin.bundle '$BUNDLE_URL'
git -C /opt/bootstrap/calvin-src fetch -q /opt/bootstrap/calvin.bundle main
git -C /opt/bootstrap/calvin-src checkout -q -f --detach FETCH_HEAD
CODE_ONLY=1 BOOTSTRAP_FETCH=0 bash /opt/bootstrap/calvin-src/deploy/vm/azure-computer-boot.sh"