From c94bc5acea16c87e3c89561a67b455306518f37d Mon Sep 17 00:00:00 2001 From: Ojus Save Date: Mon, 28 Sep 2026 13:04:01 -0700 Subject: [PATCH 1/4] Add a Render Blueprint for the API and web app. The API does not serve the Expo bundle, so the Blueprint is a Standard Node service with a persistent disk plus a static site. Deploy buttons point at CopilotKit/OpenMuse. --- README.md | 29 +++++++++++++++++++++++ render.yaml | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 render.yaml diff --git a/README.md b/README.md index 1d4bd700e..06c4ee105 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Built with CopilotKit React Native for iOS, Android, and web. [![CI](https://github.com/CopilotKit/OpenMuse/actions/workflows/ci.yml/badge.svg)](https://github.com/CopilotKit/OpenMuse/actions/workflows/ci.yml) [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/CopilotKit/OpenMuse) + Clone this template and customize it however you want. **[Building on OpenMuse? Meet with the CopilotKit team →](https://www.copilotkit.ai/openmuse)** @@ -91,6 +93,33 @@ Open [localhost:8081](http://localhost:8081). The API runs at [localhost:8787/ap For iOS or Android, use `pnpm --dir apps/mobile ios` or `pnpm --dir apps/mobile android`. Xcode or Android tooling is required. The PDF reader needs an Expo development build; use [native setup](apps/mobile/README.md). +## Deploy on Render + +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/CopilotKit/OpenMuse) + +[render.yaml](render.yaml) provisions two services. The API serves JSON at `/` and does not host the web bundle, so the UI is a separate static site. + +| Service | Type | What it runs | +|---|---|---| +| `openmuse-api` | Node web service, Standard plan, 1 GB disk at `/var/data` | The Hono API and the in-process task worker. `DATA_DIR` is `/var/data/openmuse`. The disk holds the PGlite database, PDFs, and the signing key. | +| `openmuse-web` | Static site | The Expo web export. `EXPO_PUBLIC_API_URL` is inlined at build time, so the site rebuilds when the API URL changes. | + +The API is Standard because a 512 MB instance runs out of memory before the process binds a port: PGlite loads an embedded Postgres build. The disk is required. Without it, a redeploy wipes the database and files. Chat history is stored by CopilotKit Intelligence, not on this disk, so a thread can still be there after sign-out even when the disk was missing. + +Render requires binding `0.0.0.0`. OpenMuse rejects a non-loopback host in sample mode, so the Blueprint sets `WORKSPACE_MODE=live`. Live mode needs `OPENMUSE_ACCESS_KEY` and `TOKEN_ENCRYPTION_KEY`. Render generates both. You supply two secrets when the Blueprint asks: + +- `CPK_INTELLIGENCE_API_KEY`: a CopilotKit Intelligence project key. Create one with `npx copilotkit@latest login` and `npx copilotkit@latest project select`. Required in every mode, and kept on the server. +- `OPENAI_API_KEY`: the provider key for the default `openai/gpt-4o-mini`. Change `MODEL` and swap the key to use Anthropic or Google. + +### First run + +1. Deploy with the button above. Wait until `openmuse-api` and `openmuse-web` are live. +2. On `openmuse-api`, open **Environment** and copy `OPENMUSE_ACCESS_KEY`. +3. Open the `openmuse-web` URL and sign in with that key. +4. Send a message. Chat needs both secrets from the deploy form. Workspace data, drafts, and files need the disk. + +The browser worker, the Docker computer, and Google mail or calendar are not part of this Blueprint. Each needs the extra setup in the sections below. + ## Configure the agent and Google Copy the commented settings in [.env.example](.env.example) into your private `.env`: diff --git a/render.yaml b/render.yaml new file mode 100644 index 000000000..3a98972e7 --- /dev/null +++ b/render.yaml @@ -0,0 +1,68 @@ +services: + - type: web + name: openmuse-api + runtime: node + # starter (512 MB) gets OOM-killed before the server binds a port, because + # the default PGlite store loads an embedded Postgres WASM build. + plan: standard + # No corepack: Render preinstalls pnpm at /usr/bin and /usr is read-only, so + # `corepack enable` dies with EROFS. Render's pnpm honors packageManager. + buildCommand: pnpm install --frozen-lockfile && pnpm build:server + startCommand: pnpm start + healthCheckPath: /api/health + disk: + name: openmuse-data + mountPath: /var/data + sizeGB: 1 + envVars: + # HOST must be 0.0.0.0 on Render, and OpenMuse only allows that in live mode. + - key: HOST + value: 0.0.0.0 + - key: WORKSPACE_MODE + value: live + - key: AGENT_BACKEND + value: model + - key: MODEL + value: openai/gpt-4o-mini + - key: DATA_DIR + value: /var/data/openmuse + - key: TASK_WORKER_ENABLED + value: true + - key: PUBLIC_API_URL + fromService: + name: openmuse-api + type: web + envVarKey: RENDER_EXTERNAL_URL + - key: ALLOWED_ORIGINS + fromService: + name: openmuse-web + type: web + envVarKey: RENDER_EXTERNAL_URL + - key: OPENMUSE_ACCESS_KEY + generateValue: true + - key: TOKEN_ENCRYPTION_KEY + generateValue: true + - key: OPENAI_API_KEY + sync: false + - key: CPK_INTELLIGENCE_API_KEY + sync: false + + - type: web + name: openmuse-web + runtime: static + # --clear avoids a cached Metro transform keeping a stale EXPO_PUBLIC_API_URL, + # which Expo inlines into the bundle at build time. + buildCommand: >- + pnpm install --frozen-lockfile && + pnpm --dir apps/mobile exec expo export --platform web --output-dir dist/web --clear + staticPublishPath: apps/mobile/dist/web + routes: + - type: rewrite + source: /* + destination: /index.html + envVars: + - key: EXPO_PUBLIC_API_URL + fromService: + name: openmuse-api + type: web + envVarKey: RENDER_EXTERNAL_URL From b209e2b75a91f6e47a2029574e13c17c7d35b826 Mon Sep 17 00:00:00 2001 From: Ojus Save Date: Mon, 28 Sep 2026 13:16:03 -0700 Subject: [PATCH 2/4] Rewrite the Render deploy section so a first run is scannable. Put sign-in steps and the env var table ahead of plan, disk, and live-mode limits, and say which features this Blueprint does not start. --- README.md | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 06c4ee105..ac475da2d 100644 --- a/README.md +++ b/README.md @@ -97,28 +97,40 @@ For iOS or Android, use `pnpm --dir apps/mobile ios` or `pnpm --dir apps/mobile [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/CopilotKit/OpenMuse) -[render.yaml](render.yaml) provisions two services. The API serves JSON at `/` and does not host the web bundle, so the UI is a separate static site. +[render.yaml](render.yaml) deploys two services: the API, and the web app. The API answers `/` with JSON, so the UI is its own static site. -| Service | Type | What it runs | +### First run + +1. Click **Deploy to Render**. Wait until `openmuse-api` and `openmuse-web` are both live. +2. On `openmuse-api`, open **Environment** and copy `OPENMUSE_ACCESS_KEY`. +3. Open the `openmuse-web` URL and sign in with that key. +4. Send a message. + +The deploy form asks for two values you provide. Render generates the other two. + +| Variable | Set by | If it is missing | |---|---|---| -| `openmuse-api` | Node web service, Standard plan, 1 GB disk at `/var/data` | The Hono API and the in-process task worker. `DATA_DIR` is `/var/data/openmuse`. The disk holds the PGlite database, PDFs, and the signing key. | -| `openmuse-web` | Static site | The Expo web export. `EXPO_PUBLIC_API_URL` is inlined at build time, so the site rebuilds when the API URL changes. | +| `CPK_INTELLIGENCE_API_KEY` | You. Run `npx copilotkit@latest login`, then `npx copilotkit@latest project select`. Keep it on the server. | Chat cannot open a thread. | +| `OPENAI_API_KEY` | You. Used by the default `openai/gpt-4o-mini`. Change `MODEL` and supply the matching provider key for Anthropic or Google. | The model call fails. | +| `OPENMUSE_ACCESS_KEY` | Render | You cannot sign in. | +| `TOKEN_ENCRYPTION_KEY` | Render | The API refuses to start in live mode. | -The API is Standard because a 512 MB instance runs out of memory before the process binds a port: PGlite loads an embedded Postgres build. The disk is required. Without it, a redeploy wipes the database and files. Chat history is stored by CopilotKit Intelligence, not on this disk, so a thread can still be there after sign-out even when the disk was missing. +Health check: `https:///api/health`. -Render requires binding `0.0.0.0`. OpenMuse rejects a non-loopback host in sample mode, so the Blueprint sets `WORKSPACE_MODE=live`. Live mode needs `OPENMUSE_ACCESS_KEY` and `TOKEN_ENCRYPTION_KEY`. Render generates both. You supply two secrets when the Blueprint asks: +### Services -- `CPK_INTELLIGENCE_API_KEY`: a CopilotKit Intelligence project key. Create one with `npx copilotkit@latest login` and `npx copilotkit@latest project select`. Required in every mode, and kept on the server. -- `OPENAI_API_KEY`: the provider key for the default `openai/gpt-4o-mini`. Change `MODEL` and swap the key to use Anthropic or Google. +| Service | Plan | What it runs | +|---|---|---| +| `openmuse-api` | Standard, with a 1 GB disk at `/var/data` | The Hono API and the in-process task worker. `DATA_DIR` is `/var/data/openmuse`. | +| `openmuse-web` | Static site | The Expo web export. `EXPO_PUBLIC_API_URL` is baked in at build time. | -### First run +**Standard** is the smallest plan that stays up. At 512 MB the process runs out of memory before it binds a port, because PGlite loads an embedded Postgres build. -1. Deploy with the button above. Wait until `openmuse-api` and `openmuse-web` are live. -2. On `openmuse-api`, open **Environment** and copy `OPENMUSE_ACCESS_KEY`. -3. Open the `openmuse-web` URL and sign in with that key. -4. Send a message. Chat needs both secrets from the deploy form. Workspace data, drafts, and files need the disk. +**The disk** holds the database, PDFs, and the signing key. A redeploy without it wipes that data. Chat threads are stored by CopilotKit Intelligence, so a thread can still load after you sign back in even when the disk was never attached. + +**Live mode** is required. Render binds `0.0.0.0`, and sample mode rejects any host that is not loopback. The Blueprint sets `WORKSPACE_MODE=live`. -The browser worker, the Docker computer, and Google mail or calendar are not part of this Blueprint. Each needs the extra setup in the sections below. +The browser worker, the Docker computer, and Google mail or calendar need the setup in the sections below. This Blueprint does not start them. ## Configure the agent and Google From a759dd4bca19220e99ac2b9fd909472b142f9446 Mon Sep 17 00:00:00 2001 From: Ojus Save Date: Mon, 28 Sep 2026 14:24:44 -0700 Subject: [PATCH 3/4] Add an optional private browser service to the Blueprint. The API already runs without it. Removing the service and its two env vars leaves chat and tasks working, and skips Chromium. --- README.md | 5 ++++- render.yaml | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac475da2d..bab45546a 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ Health check: `https:///api/health`. |---|---|---| | `openmuse-api` | Standard, with a 1 GB disk at `/var/data` | The Hono API and the in-process task worker. `DATA_DIR` is `/var/data/openmuse`. | | `openmuse-web` | Static site | The Expo web export. `EXPO_PUBLIC_API_URL` is baked in at build time. | +| `openmuse-browser` | Private service, Standard, 1 GB disk at `/data` | Playwright and Chromium. The API calls it on the private network. | **Standard** is the smallest plan that stays up. At 512 MB the process runs out of memory before it binds a port, because PGlite loads an embedded Postgres build. @@ -130,7 +131,9 @@ Health check: `https:///api/health`. **Live mode** is required. Render binds `0.0.0.0`, and sample mode rejects any host that is not loopback. The Blueprint sets `WORKSPACE_MODE=live`. -The browser worker, the Docker computer, and Google mail or calendar need the setup in the sections below. This Blueprint does not start them. +**Browsing is included, and you can take it out.** `openmuse-browser` is a private service, so it has no public URL. The API reaches it at `http://openmuse-browser:8790` with a token Render generates. To deploy without it, delete the `openmuse-browser` service and the `BROWSER_WORKER_URL` and `WORKER_TOKEN` entries on `openmuse-api`. Chat, drafts, and tasks still run. Page reads, screenshots, and **Take control** do not. + +The Docker computer and Google mail or calendar need the setup in the sections below. This Blueprint does not start them. ## Configure the agent and Google diff --git a/render.yaml b/render.yaml index 3a98972e7..33d442d79 100644 --- a/render.yaml +++ b/render.yaml @@ -46,6 +46,35 @@ services: sync: false - key: CPK_INTELLIGENCE_API_KEY sync: false + # Delete these two entries, and the openmuse-browser service below, to + # deploy without browsing. The API boots either way. + - key: BROWSER_WORKER_URL + value: http://openmuse-browser:8790 + - key: WORKER_TOKEN + fromService: + name: openmuse-browser + type: pserv + envVarKey: WORKER_TOKEN + + # Private Chromium for page reads, screenshots, and Take control. + # Remove this service to skip it. The image listens on 8790, not $PORT. + - type: pserv + name: openmuse-browser + runtime: docker + plan: standard + dockerfilePath: ./apps/worker/Dockerfile + dockerContext: ./apps/worker + disk: + name: openmuse-browser-data + mountPath: /data + sizeGB: 1 + envVars: + - key: WORKER_TOKEN + generateValue: true + - key: WORKER_HOST + value: 0.0.0.0 + - key: WORKER_DATA_DIR + value: /data - type: web name: openmuse-web From b7e63da767d29ea6203613a6555c5638678f0969 Mon Sep 17 00:00:00 2001 From: Ojus Save Date: Tue, 29 Sep 2026 09:59:33 -0700 Subject: [PATCH 4/4] Document all three Blueprint services before first sign-in. The intro and first-run step still described only the API and web app, so someone could sign in before the browser service was up. The default model now matches the gpt-5 id used elsewhere in the repo. --- README.md | 8 ++++---- render.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bab45546a..6b662c45b 100644 --- a/README.md +++ b/README.md @@ -97,11 +97,11 @@ For iOS or Android, use `pnpm --dir apps/mobile ios` or `pnpm --dir apps/mobile [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/CopilotKit/OpenMuse) -[render.yaml](render.yaml) deploys two services: the API, and the web app. The API answers `/` with JSON, so the UI is its own static site. +[render.yaml](render.yaml) deploys three services: the API, the web app, and a private browser. The API answers `/` with JSON, so the UI is its own static site. ### First run -1. Click **Deploy to Render**. Wait until `openmuse-api` and `openmuse-web` are both live. +1. Click **Deploy to Render**. Wait until `openmuse-api`, `openmuse-web`, and `openmuse-browser` are live. 2. On `openmuse-api`, open **Environment** and copy `OPENMUSE_ACCESS_KEY`. 3. Open the `openmuse-web` URL and sign in with that key. 4. Send a message. @@ -111,7 +111,7 @@ The deploy form asks for two values you provide. Render generates the other two. | Variable | Set by | If it is missing | |---|---|---| | `CPK_INTELLIGENCE_API_KEY` | You. Run `npx copilotkit@latest login`, then `npx copilotkit@latest project select`. Keep it on the server. | Chat cannot open a thread. | -| `OPENAI_API_KEY` | You. Used by the default `openai/gpt-4o-mini`. Change `MODEL` and supply the matching provider key for Anthropic or Google. | The model call fails. | +| `OPENAI_API_KEY` | You. Used by the default `openai/gpt-5`. Change `MODEL` and supply the matching provider key for Anthropic or Google. | The model call fails. | | `OPENMUSE_ACCESS_KEY` | Render | You cannot sign in. | | `TOKEN_ENCRYPTION_KEY` | Render | The API refuses to start in live mode. | @@ -131,7 +131,7 @@ Health check: `https:///api/health`. **Live mode** is required. Render binds `0.0.0.0`, and sample mode rejects any host that is not loopback. The Blueprint sets `WORKSPACE_MODE=live`. -**Browsing is included, and you can take it out.** `openmuse-browser` is a private service, so it has no public URL. The API reaches it at `http://openmuse-browser:8790` with a token Render generates. To deploy without it, delete the `openmuse-browser` service and the `BROWSER_WORKER_URL` and `WORKER_TOKEN` entries on `openmuse-api`. Chat, drafts, and tasks still run. Page reads, screenshots, and **Take control** do not. +**Browsing is included, and you can take it out.** `openmuse-browser` is a private service, so it has no public URL. The API reaches it at `http://openmuse-browser:8790` with a token Render generates. If the private hostname is not `openmuse-browser`, set `BROWSER_WORKER_URL` to `http://:8790`. To deploy without it, delete the `openmuse-browser` service and the `BROWSER_WORKER_URL` and `WORKER_TOKEN` entries on `openmuse-api`. Chat, drafts, and tasks still run. Page reads, screenshots, and **Take control** do not. The Docker computer and Google mail or calendar need the setup in the sections below. This Blueprint does not start them. diff --git a/render.yaml b/render.yaml index 33d442d79..88ea05201 100644 --- a/render.yaml +++ b/render.yaml @@ -23,7 +23,7 @@ services: - key: AGENT_BACKEND value: model - key: MODEL - value: openai/gpt-4o-mini + value: openai/gpt-5 - key: DATA_DIR value: /var/data/openmuse - key: TASK_WORKER_ENABLED