Deploy OpenDots | (Just Updated) AI Agent Workspace With Pages API, Owner Token Generated

OpenDots self-hosted AI agent workspace with pages API and SQLite

Deploy OpenDots | (Just Updated) AI Agent Workspace With Pages API, Owner Token Generated

Just deployed

/data

Deploy and Host OpenDots on Railway

Built for an LLM agent to deploy and use. The deploy form has nothing to fill in: the owner token is generated, the REST API is reachable with it as a bearer token, and your workspace (a SQLite database on a volume) survives redeploys. OpenDots is the open-source agent workspace from CopilotKit: spaces, pages and chat with AI teammates in one self-hosted web app, with a JSON API behind it.

About Hosting OpenDots

This template runs one service, opendots, built from the upstream CopilotKit/OpenDots server (a Hono and SQLite server that also serves the web app) pinned to a source commit, wrapped as ghcr.io/bon5co/opendots-railway and pinned by digest. A volume at /data holds the SQLite database, so spaces and pages are kept across redeploys.

No open workspace. Every /api route requires Authorization: Bearer OWNER_TOKEN. The token is generated at deploy (48 characters) and the wrapper refuses to start with an empty or short one. The web app itself loads without it, and asks for the token to open the workspace.

What the wrapper fixes for Railway. The upstream image does not exist, so the wrapper builds the server from source. It sets the public origin from the generated domain (OpenDots checks the browser origin against APP_ORIGIN), listens on Railway's injected PORT, and chowns the root-owned volume for the unprivileged node user so writes reach disk.

Optional model and Intelligence keys. Chat with AI teammates needs a CopilotKit Intelligence key and an OpenAI key, which are yours and are not template variables. Add INTELLIGENCE_API_KEY and OPENAI_API_KEY as variables on the opendots service to enable it. Without them the workspace still runs and pages can be created and edited by hand and over the API.

Agent contract

  • Public URL: https://RAILWAY_PUBLIC_DOMAIN (the generated domain of the opendots service)
  • Private URL: http://opendots.railway.internal:8080
  • Credentials: the variable OWNER_TOKEN on the opendots service
  • Auth: send Authorization: Bearer OWNER_TOKEN on every /api call, with Content-Type: application/json on writes. A request with no token or a wrong token answers 401.
  • Health: GET / returns 200
#!/bin/sh
# needs: RAILWAY_PUBLIC_DOMAIN, OWNER_TOKEN (variables of the opendots service); also curl.
R="https://$RAILWAY_PUBLIC_DOMAIN"; A="Authorization: Bearer $OWNER_TOKEN"; J="Content-Type: application/json"; B=$(mktemp)
n=0; until [ "$(curl -s -o /dev/null -w '%{http_code}' "$R/")" = 200 ]; do n=$((n+1)); [ "$n" -le 60 ] || break; sleep 5; done
curl -s -o /dev/null -w "health: %{http_code}\n" "$R/"
curl -s -o "$B" -w "read with token: %{http_code}\n" -H "$A" "$R/api/workspace"
S=$(sed -n 's/.*"spaces":\[{"id":"\([^"]*\)".*/\1/p' "$B")
curl -s -o "$B" -w "write with token: %{http_code}\n" -H "$A" -H "$J" -d '{"title":"railway-check","content":"written by the agent contract"}' "$R/api/spaces/$S/pages"
P=$(sed -n 's/^{"id":"\([^"]*\)".*/\1/p' "$B")
curl -s -H "$A" "$R/api/spaces/$S/pages" | grep -c "railway-check" | sed 's/^/page found after write: /'
curl -s -o /dev/null -w "delete with token: %{http_code}\n" -X DELETE -H "$A" -H "$J" "$R/api/spaces/$S/pages/$P"
curl -s -o /dev/null -w "read without token: %{http_code}\n" "$R/api/workspace"
curl -s -o /dev/null -w "write without key: %{http_code}\n" -H "$J" -d '{"title":"anonymous","content":"x"}' "$R/api/spaces/$S/pages"
curl -s -o /dev/null -w "write wrong token: %{http_code}\n" -H "Authorization: Bearer wrong" -H "$J" -d '{"title":"anonymous","content":"x"}' "$R/api/spaces/$S/pages"

A fresh deploy prints health: 200, read with token: 200, write with token: 201, page found after write: 1 and delete with token: 200. The refusals print read without token: 401, write without key: 401 and write wrong token: 401: a request with no token or a wrong one creates nothing.

Why Deploy OpenDots on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying OpenDots on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.

  • Agent-ready - a generated token, a contract block and one script that proves health, an authenticated write, read-back, delete, and the refusal of a read and a write with no token or a wrong one.
  • No setup form and no open API - the owner token is generated and every /api route needs it.
  • Data survives redeploys - the SQLite database sits on a volume at /data.
  • Works behind the Railway edge - the listen port follows PORT and the public origin is set from the generated domain.
  • Pinned - the upstream source is pinned to a commit and the wrapper image by tag and digest.

Common Use Cases

  • Shared agent workspace - give a team and its AI agents one self-hosted place for spaces and pages.
  • Agent-written documentation - an agent creates, reads and removes pages over the REST API.
  • Self-hosted CopilotKit workspace - run the OpenDots app on infrastructure you control instead of a hosted service.
  • Notes and knowledge base for AI agents - keep pages in SQLite on your own volume and read them from scripts.

Dependencies for OpenDots Hosting

  • The opendots service with its volume, created by the template.

Deployment Dependencies


Template Content

More templates in this category

View Template
Rocky Linux
Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀

codestorm
51
View Template
Foundry Virtual Tabletop
A Self-Hosted & Modern Roleplaying Platform

Lucas
71
View Template
Letta Code Remote
Run a Letta Code agent 24/7. No inbound ports, just deploy.

Letta
52