Deployment: Railway Pro
Domain: kublai.kurult.ai
Channel: Signal
Owner: Kurultai LLC
Last Updated: 2026-01-31
This document describes the production deployment of OpenClaw (Moltbot) on Railway with Signal integration. Use this as a reference to replicate or restore the configuration.
┌─────────────────────────────────────┐
│ Railway Pro │
┌──────────────┐ │ ┌───────────────────────────────┐ │
│ │ │ │ moltbot-railway-template │ │
│ Signal │◄──────────────────►│ │ │ │
│ (Phone) │ Signal Protocol │ │ ┌─────────┐ ┌──────────┐ │ │
│ │ │ │ │ Gateway │◄───│ Agent │ │ │
└──────────────┘ │ │ │ :8080 │ │ (Claude) │ │ │
│ │ └────┬────┘ └──────────┘ │ │
┌──────────────┐ │ │ │ │ │
│ │ HTTPS │ │ ▼ │ │
│ Control UI │◄──────────────────►│ │ ┌─────────┐ │ │
│ (Browser) │ kublai.kurult.ai │ │ │ Volume │ │ │
│ │ │ │ │ /data │ │ │
└──────────────┘ │ │ └─────────┘ │ │
│ └───────────────────────────────┘ │
│ │
│ ┌───────────────────────────────┐ │
│ │ signal-cli-native (sidecar) │ │
│ │ Internal: :8080 │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
| Property | Value |
|---|---|
| Project Name | clever-blessing |
| Environment | production |
| Template | codetitlan/moltbot-railway-template |
| Region | US East (default) |
| Property | Value |
|---|---|
| Image | Built from template |
| Port (External) | 8080 |
| Port (Internal Gateway) | 18789 |
| Volume | /data (persistent) |
| Custom Domain | kublai.kurult.ai |
| Property | Value |
|---|---|
| Purpose | Signal protocol bridge |
| Internal URL | http://signal-cli-native.railway.internal:8080 |
| Visibility | Internal only (no public access) |
| Record Type | Name | Value | TTL |
|---|---|---|---|
| CNAME | kublai |
xod4zi0r.up.railway.app |
600 |
DNS Provider: GoDaddy SSL: Auto-provisioned by Railway (Let's Encrypt)
CRITICAL: Railway's container environment has specific limitations that affect OpenClaw configuration.
Railway containers cannot run Docker. This affects:
| Feature | Impact | Required Setting |
|---|---|---|
| Agent Sandboxing | Cannot use Docker sandbox | agents.defaults.sandbox.mode: "off" |
| Browser Tools | Cannot spawn browser containers | browser.enabled: false |
| Computer Use | Cannot use desktop automation | tools.profile: "coding" |
If sandbox mode is not disabled, the gateway will crash with:
Error: spawn docker ENOENT
Railway routes all traffic through edge proxies. The gateway must trust these proxies:
"gateway": {
"trustedProxies": ["*"]
}Without this, you'll see warnings:
Proxy headers detected from untrusted address. Connection will not be treated as local.
| Setting | Value | Reason |
|---|---|---|
agents.defaults.sandbox.mode |
"off" |
No Docker available |
browser.enabled |
false |
No browser containers |
tools.profile |
"coding" |
Excludes browser/computer tools |
gateway.trustedProxies |
["*"] |
Accept Railway proxy headers |
# Authentication (SECURITY-CRITICAL - generate unique values)
SETUP_PASSWORD=<32-char-random-password>
CLAWDBOT_GATEWAY_TOKEN=<64-char-random-token>
OPENCLAW_GATEWAY_TOKEN=<64-char-random-token> # Alternative name
# AI Provider
ANTHROPIC_API_KEY=sk-ant-...
# Storage paths
CLAWDBOT_STATE_DIR=/data/.clawdbot
CLAWDBOT_WORKSPACE_DIR=/data/workspace# SETUP_PASSWORD (32 chars, ~192 bits entropy)
openssl rand -base64 24
# GATEWAY_TOKEN (64 chars, ~384 bits entropy)
openssl rand -base64 48# Additional AI providers
OPENAI_API_KEY=sk-...
OPENROUTER_API_KEY=sk-or-...
# Other channels (if adding later)
TELEGRAM_BOT_TOKEN=123456:ABC...
DISCORD_BOT_TOKEN=...
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-.../data/.clawdbot/moltbot.json (on Railway volume)
Also committed locally at: /Users/kurultai/molt/moltbot.json
{
"gateway": {
"mode": "local",
"port": 18789,
"trustedProxies": ["*"], // Required: Railway proxy headers
"auth": {
"mode": "token",
"token": "${OPENCLAW_GATEWAY_TOKEN}"
},
"controlUi": {
"enabled": true
}
},
"agents": {
"defaults": {
"workspace": "/data/workspace",
"sandbox": {
"mode": "off" // CRITICAL: Railway has no Docker-in-Docker
}
}
},
"channels": {
"signal": {
"enabled": true,
"account": "+15165643945",
"httpUrl": "http://signal-cli-native.railway.internal:8080",
"autoStart": false,
"dmPolicy": "pairing",
"configWrites": false,
"allowFrom": ["+15165643945", "+19194133445"],
"groupAllowFrom": ["+19194133445"],
"historyLimit": 50,
"textChunkLimit": 4000,
"ignoreStories": true
}
},
"session": {
"scope": "per-sender",
"reset": {
"mode": "daily"
}
},
"logging": {
"level": "info"
},
"browser": {
"enabled": false // Disable browser tools on Railway
},
"tools": {
"profile": "coding" // Limit to coding tools (excludes browser)
}
}| Section | Setting | Purpose |
|---|---|---|
gateway.mode |
local |
Gateway runs behind Railway's proxy |
gateway.port |
18789 |
Internal gateway port |
gateway.trustedProxies |
["*"] |
Trust Railway's proxy headers (prevents "untrusted address" warnings) |
gateway.auth.mode |
token |
Token-based authentication |
gateway.controlUi.enabled |
true |
Web UI at root URL |
agents.defaults.workspace |
/data/workspace |
Persistent file storage |
agents.defaults.sandbox.mode |
off |
CRITICAL: Railway has no Docker - sandbox must be disabled |
channels.signal.account |
+15165643945 |
Linked Signal phone number |
channels.signal.httpUrl |
Internal Railway URL | Signal CLI sidecar |
channels.signal.allowFrom |
Phone numbers | Allowlisted users |
channels.signal.dmPolicy |
pairing |
DM handling mode |
session.scope |
per-sender |
Separate sessions per user |
session.reset.mode |
daily |
Reset sessions daily |
browser.enabled |
false |
Browser tools disabled on Railway |
tools.profile |
coding |
Limit to coding tools (excludes browser/computer tools) |
| Property | Value |
|---|---|
| Phone Number | +15165643945 |
| Protocol | Signal Protocol (E2EE) |
| Bridge | signal-cli-native sidecar |
| Phone Number | Role |
|---|---|
+15165643945 |
Owner (self) |
+19194133445 |
Authorized user |
- Access Control UI:
https://kublai.kurult.ai/ - Navigate to Channels > Signal
- Click "Link Device" to generate QR code
- On phone: Signal > Settings > Linked Devices > Link New Device
- Scan QR code
| Type | URL |
|---|---|
| Base URL | https://kublai.kurult.ai/ |
| Tokenized URL | https://kublai.kurult.ai/?token=<GATEWAY_TOKEN> |
| Chat | https://kublai.kurult.ai/chat |
| Channels | https://kublai.kurult.ai/channels |
| Sessions | https://kublai.kurult.ai/sessions |
| Settings | https://kublai.kurult.ai/settings |
| Skills | https://kublai.kurult.ai/skills |
| Resource | URL |
|---|---|
| Project | https://railway.com/project/9a3e528b-28b5-44f1-b037-9bb0ad4f992b |
| Service Settings | https://railway.com/project/.../service/.../settings |
| Logs | https://railway.com/project/.../service/.../logs |
| Variables | https://railway.com/project/.../service/.../variables |
| URL | Purpose |
|---|---|
https://moltbot-railway-template-production-75e7.up.railway.app/ |
Original Railway domain |
/data/
├── .clawdbot/ # State directory
│ ├── moltbot.json # Main configuration
│ ├── credentials/ # Channel credentials
│ │ └── signal/ # Signal linked device data
│ └── sessions/ # Session persistence
├── workspace/ # Agent workspace
│ └── (user files)
└── backups/ # Local backups (optional)
# Via Railway CLI
railway init
railway link
# Or via browser
# Navigate to: https://railway.com/deploy/moltbot-railway-templateIn Railway Dashboard > Variables:
SETUP_PASSWORD=$(openssl rand -base64 24)
CLAWDBOT_GATEWAY_TOKEN=$(openssl rand -base64 48)
ANTHROPIC_API_KEY=sk-ant-your-key-here
CLAWDBOT_STATE_DIR=/data/.clawdbot
CLAWDBOT_WORKSPACE_DIR=/data/workspace- Railway > Service > Settings > Networking > Custom Domain
- Add:
kublai.kurult.ai - Copy CNAME value
- Add DNS record at GoDaddy:
- Type: CNAME
- Name:
kublai - Value:
<railway-cname>.up.railway.app
- Wait for verification (5-15 minutes)
- Access:
https://kublai.kurult.ai/setup - Enter SETUP_PASSWORD
- Complete setup wizard
- Navigate to Channels > Signal
- Link device via QR code
Copy configuration to /data/.clawdbot/moltbot.json or edit via Control UI.
Update channels.signal.allowFrom array with authorized phone numbers.
| Path | Contents | Priority |
|---|---|---|
/data/.clawdbot/moltbot.json |
Configuration | Critical |
/data/.clawdbot/credentials/ |
Channel auth | Critical |
/data/workspace/ |
User files | High |
| Environment variables | API keys, tokens | Critical |
# SSH into Railway or run via Control UI terminal
tar -czf /data/backups/moltbot-$(date +%Y%m%d).tar.gz \
/data/.clawdbot \
/data/workspace- Deploy fresh template
- Set same environment variables
- Restore
/datavolume contents - Re-link Signal if credentials expired
In addition to moltbot.json, OpenClaw uses a separate configuration file for model providers and authentication profiles.
/data/.clawdbot/openclaw.json (on Railway volume) or ~/.openclaw/openclaw.json locally
This configuration is typically managed through the Control UI at Settings > OpenClaw.
CRITICAL: When configuring custom model providers (including overriding built-in providers like Anthropic), the models array is REQUIRED. Without it, the provider configuration is invalid and the LLM will not be called.
{
"auth": {
"profiles": {
"anthropic:default": {
"mode": "api_key"
}
}
},
"models": {
"providers": {
"anthropic": {
"api": "anthropic-messages",
"apiKey": "${ANTHROPIC_API_KEY}",
"baseUrl": "${ANTHROPIC_BASE_URL}",
"models": [
{
"id": "claude-sonnet-4-20250514",
"name": "Claude Sonnet 4",
"reasoning": false,
"input": ["text"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
}
}
]
}
}
}
}| Field | Required | Description |
|---|---|---|
api |
Yes | Protocol type: anthropic-messages, openai-completions, etc. |
apiKey |
Yes | API key using env var syntax ${VARIABLE_NAME} |
baseUrl |
Yes* | API endpoint URL (*required for custom endpoints) |
models |
Yes | Array of available models - MUST have at least one entry |
| Field | Required | Description |
|---|---|---|
id |
Yes | Model identifier (e.g., claude-sonnet-4-20250514) |
name |
Yes | Display name (e.g., Claude Sonnet 4) |
reasoning |
No | Whether model supports reasoning traces |
input |
No | Supported input types array: ["text"], ["text", "image"] |
cost |
No | Token cost configuration (set to 0 for flat-rate/proxy APIs) |
When using an API proxy that wraps Anthropic's API:
# Environment Variables (Railway)
ANTHROPIC_API_KEY=your-proxy-api-key
ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropicThe baseUrl must point to your proxy endpoint while keeping the api type as anthropic-messages.
# Check logs
railway logs
# Verify env vars
railway variables- Verify signal-cli-native sidecar is running
- Check
httpUrlmatches sidecar internal URL - Re-link device if session expired
- Verify sender phone in
allowFromlist - Check logs for blocked messages
- Ensure Signal linked device is still active
Symptoms: Messages received but rejected with error:
receive exception: Untrusted identity: <uuid>
Cause: The sender's Signal identity key has changed (reinstalled Signal, new device, etc.)
Fix via JSON-RPC API:
# Trust the identity (replace phone numbers as needed)
curl -X POST "https://signal-cli-native-production.up.railway.app/api/v1/rpc" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "trust",
"params": {
"account": "+15165643945",
"recipient": "+19194133445",
"trustAllKnownKeys": true
},
"id": 1
}'Verify trust status:
curl -X POST "https://signal-cli-native-production.up.railway.app/api/v1/rpc" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "listIdentities",
"params": { "account": "+15165643945" },
"id": 2
}'Look for "trustLevel": "TRUSTED_UNVERIFIED" for the recipient.
Note: The signal-cli-native service exposes JSON-RPC at /api/v1/rpc on its public Railway URL.
Symptoms: Gateway crashes when processing messages, logs show:
Uncaught exception: Error: spawn docker ENOENT
Cause: agents.defaults.sandbox.mode is set to "all" or "non-main", attempting Docker sandboxing on Railway where Docker isn't available.
Fix: Set sandbox mode to "off" in moltbot.json:
{
"agents": {
"defaults": {
"sandbox": {
"mode": "off"
}
}
}
}Also set tools.profile: "coding" and browser.enabled: false to prevent browser tools from being invoked.
- Verify DNS CNAME record
- Check Railway domain verification status
- Wait for SSL certificate provisioning
Symptoms:
- Chatbot returns empty assistant responses
- Agent runs complete in <100ms with 0 tokens used
- No errors in logs but LLM is clearly not invoked
Root Cause: The models.providers configuration is missing the required models array.
Diagnosis:
- Access Control UI > Settings > OpenClaw > Raw JSON
- Check if
models.providers.anthropic(or your provider) has amodelsarray - If
modelsis missing or empty, this is the issue
Fix via Control UI:
- Settings > OpenClaw > Form view
- Navigate to Models > Providers > anthropic (or your provider)
- Scroll to "Models" section (shows "0 items" if broken)
- Click "+ Add" to add a model entry
- Fill in required fields:
- Id:
claude-sonnet-4-20250514(or your model) - Name:
Claude Sonnet 4(display name)
- Id:
- Click Save
- Click Apply to restart with new config
Fix via Raw JSON:
Add the models array to your provider config:
"models": {
"providers": {
"anthropic": {
"api": "anthropic-messages",
"apiKey": "${ANTHROPIC_API_KEY}",
"baseUrl": "${ANTHROPIC_BASE_URL}",
"models": [
{
"id": "claude-sonnet-4-20250514",
"name": "Claude Sonnet 4"
}
]
}
}
}Important: The models array is REQUIRED for custom provider configurations, even when using built-in provider types like "anthropic".
Symptoms:
- Raw JSON editor shows "valid" badge but save fails
- Form view changes don't persist
- Multiple save attempts all fail
Common Causes:
- Missing required
modelsarray in provider config - Invalid JSON syntax (extra commas, missing quotes)
- Unknown configuration keys
Fix:
- Check the browser console (F12) for detailed validation errors
- Verify provider config has all required fields (see Model Provider Configuration section)
- Use Form view to add missing fields rather than manual JSON editing
- SETUP_PASSWORD is strong (32+ chars)
- GATEWAY_TOKEN is strong (64+ chars)
- Tokens not committed to git
- allowFrom restricts to known users
- HTTPS enforced (Railway default)
- API keys in environment variables only
- Regular credential rotation scheduled
- Railway > Service > Settings > Source
- Look for "New version of upstream repo available"
- Click "Update" to pull latest
- Note current working state
- Export configuration backup
- Apply update
- Verify services restart correctly
- Test Signal connectivity
Warning: Updates restart the container and interrupt active sessions.
- OpenClaw Documentation
- Railway Documentation
- Signal CLI Documentation
- Local skill reference:
~/.claude/skills/molt/