/docs
Guide
Railway and VPS

Run Spark always-on without guessing.

Use this when Spark is hosted outside a personal laptop. The goal is boring production: two services, private relay, pinned code, explicit keys, and a smoke test that proves Telegram can reach Mission Control.

Use the two-service shape.

Spark Live / Spawner

Runs Mission Control, workspaces, provider execution, previews, and board state.

SPAWNER_STATE_DIR=/data/spawner

Telegram bot

Runs Telegram long polling, access control, and the private mission relay.

TELEGRAM_RELAY_PORT=8788

Private bridge

Use one shared bridge key for bot-to-Spawner calls. Use a different UI key for browser login.

SPARK_BRIDGE_API_KEY

Railway rules worth remembering.

Railway deploys application code with railway up. Healthchecks should return HTTP 200 on the configured path and the app should listen on Railway's PORT. Services in the same environment can call each other over private DNS such as service.railway.internal.

Deploy code

Use this when the code changed.

railway up

Redeploy same code

Use this after environment changes or a restart.

railway redeploy

Read bounded logs

Start with a small log pull before streaming.

railway logs --lines 100

Environment checklist.

Spawner service

Public UI, private state.

SPARK_HOSTED_PRIVATE_PREVIEW=1
SPARK_WORKSPACE_ID=<workspace-id>
SPARK_ALLOWED_HOSTS=<public-spawner-host>
SPARK_UI_API_KEY=<browser-login-key>
SPARK_BRIDGE_API_KEY=<shared-bot-spawner-key>
SPAWNER_STATE_DIR=/data/spawner
SPARK_WORKSPACE_ROOT=/data/spark/workspaces
CODEX_HOME=/data/codex
Telegram bot service

Long polling plus private relay.

TELEGRAM_BOT_TOKEN=<botfather-token>
SPARK_TELEGRAM_ADMIN_IDS=<telegram-user-id>
TELEGRAM_RELAY_HOST=::
TELEGRAM_RELAY_PORT=8788
SPAWNER_UI_URL=http://<spawner-service>.railway.internal:<port>
SPAWNER_UI_PUBLIC_URL=https://<protected-spawner-public-domain>
MISSION_CONTROL_WEBHOOK_URLS=http://<bot-service>.railway.internal:8788/spawner-events
SPARK_BRIDGE_API_KEY=<same-shared-bot-spawner-key>

Key rule

SPAWNER_UI_URL is for private service-to-service calls. SPAWNER_UI_PUBLIC_URL is for Telegram links that humans open in a browser.

Hosted provider rules.

Use API keys

Z.AI, OpenAI, Anthropic, OpenRouter, Kimi, MiniMax, Hugging Face, and OpenAI-compatible providers are the clean hosted path.

Codex hosted path

Use a dedicated OPENAI_API_KEY and persistent CODEX_HOME. Do not copy personal desktop OAuth.

Claude hosted path

Use ANTHROPIC_API_KEY. Keep Claude Code OAuth on the local desktop.

Local models

Ollama, LM Studio, vLLM, TGI, and llama.cpp must be reachable endpoints. Do not download large models into the Spark app container.

Production smoke.

First prove the public Spawner surface. Then prove Telegram can start and finish a tiny mission through the private relay.

curl -fsS https://<spawner-public-domain>/api/health/live
curl -fsS https://<spawner-public-domain>/api/providers
curl -fsS https://<spawner-public-domain>/api/mission-control/board
/diagnose
/run Build a tiny static HTML page called Spark Production Smoke. It should have one file, index.html, with a dark Mission Control panel, a green "Spark Live OK" status, and the text "Telegram to Spawner relay worked on May 8, 2026". Do not add package files, do not install dependencies, and keep it simple enough to finish fast.

Spawner: UI is reachable and provider metadata is visible.

Access: `/diagnose` shows the current Telegram user is allowed.

Mission: provider ping succeeds and the smoke mission completes.

Workspace: one-file prompts produce exactly the expected static file shape.

Common hosted fixes.

Spawner is HTTP 401

Check SPARK_UI_API_KEY for browsers and SPARK_BRIDGE_API_KEY for bot relay calls. Do not make Spawner public to fix auth.

Mission board unreachable

Check SPAWNER_UI_URL from the bot service. On Railway it should usually be private DNS.

Telegram links open private DNS

Set SPAWNER_UI_PUBLIC_URL to the protected public Spawner domain.

Plain chat fails, missions work

Fix chat provider key, base URL, or model, then restart Telegram. Mission routing can still be healthy.

Provider CLI exists but fails

Installed does not mean authenticated. Use hosted API keys instead of desktop OAuth state.

Old code still appears

Check deployment IDs and service selection. Use railway up for code changes and railway redeploy for same-code restarts.

VPS notes.

Use the same two-service shape with Docker Compose or a process supervisor. Put Nginx, Caddy, or another reverse proxy in front of Spawner, terminate TLS there, and keep the bot relay private to the host or overlay network.

Backups before upgrades

Back up /data/spawner, /data/spark/workspaces, and provider config before production upgrades.

Release rule.

Do not point the public installer or agent.sparkswarm.ai release manifest at a new Spark CLI commit until registry pins, provenance, installer verification, Railway smoke, and at least one Telegram /run smoke are green.