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.
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
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.