| name | deploy-moltbot-to-fly |
| description | Deploy Moltbot (Clawdbot) to Fly.io with proper proxy configuration, trusted proxies setup, and device pairing |
Deploy Moltbot to Fly.io
You are helping the user deploy Moltbot (also known as Clawdbot) to Fly.io. This is a comprehensive guide that handles all the tricky configuration including trusted proxies for Fly's internal network and device pairing approval.
Overview
Deploying Moltbot to Fly.io requires:
- Setting up the Fly app with a persistent volume
- Configuring environment secrets (API keys, gateway token)
- Setting up
trustedProxies to allow Fly's internal proxy network
- Approving device pairing for web UI access
Prerequisites
Before starting:
Phase 1: Clone and Setup
1.1 Clone the Moltbot Repository
git clone https://github.com/moltbot/moltbot.git
cd moltbot
1.2 Create .env.example
Create a .env.example file in the project root with the required environment variables:
cat > .env.example << 'EOF'
ANTHROPIC_API_KEY=sk-ant-xxxxx
OPENAI_API_KEY=sk-xxxxx
CLAWDBOT_GATEWAY_TOKEN=your-64-char-hex-token
SLACK_BOT_TOKEN=xoxb-xxxxx
DISCORD_BOT_TOKEN=your-discord-bot-token
EOF
Tell the user:
"Please copy .env.example to .env and fill in your API keys. At minimum, you need:
ANTHROPIC_API_KEY: Your Anthropic API key
CLAWDBOT_GATEWAY_TOKEN: Generate with openssl rand -hex 32"
1.3 Generate Gateway Token
openssl rand -hex 32
Save this token - you'll need it for Fly secrets and for web UI authentication.
Phase 2: Fly.io Configuration
2.1 Create fly.toml
Create or update fly.toml in the project root:
app = 'your-app-name'
primary_region = 'iad'
[build]
dockerfile = 'Dockerfile'
[env]
NODE_ENV = 'production'
TZ = 'UTC'
[processes]
app = "node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan"
[[mounts]]
source = 'moltbot_data'
destination = '/data'
[[vm]]
size = 'shared-cpu-2x'
memory = '2gb'
[[services]]
internal_port = 3000
protocol = 'tcp'
[[services.ports]]
handlers = ['http']
port = 80
force_https = true
[[services.ports]]
handlers = ['tls', 'http']
port = 443
CRITICAL: The --bind lan flag is essential. It tells the gateway to bind to the LAN interface which maps to 0.0.0.0 inside the Fly VM.
2.2 Create the Fly App
fly apps create your-app-name
2.3 Create Persistent Volume
fly volumes create moltbot_data --region iad --size 1
2.4 Set Fly Secrets
fly secrets set ANTHROPIC_API_KEY="sk-ant-xxxxx" -a your-app-name
fly secrets set CLAWDBOT_GATEWAY_TOKEN="$(openssl rand -hex 32)" -a your-app-name
fly secrets set OPENAI_API_KEY="sk-xxxxx" -a your-app-name
fly secrets set SLACK_BOT_TOKEN="xoxb-xxxxx" -a your-app-name
fly secrets set DISCORD_BOT_TOKEN="your-token" -a your-app-name
Important: Save the CLAWDBOT_GATEWAY_TOKEN value - you'll need it later for device pairing.
To retrieve the token later:
fly ssh console -a your-app-name -C "printenv CLAWDBOT_GATEWAY_TOKEN"
Phase 3: Deploy
3.1 Deploy to Fly.io
fly deploy -a your-app-name
The first deploy may take a few minutes. Watch for the "listening on ws://0.0.0.0:3000" message in the logs.
3.2 Verify Deployment
fly logs -a your-app-name --no-tail | tail -30
Look for:
[gateway] listening on ws://0.0.0.0:3000
[slack] socket mode connected (if using Slack)
Phase 4: Configure Trusted Proxies
CRITICAL STEP: Fly.io routes traffic through internal proxies (172.16.x.x). Without configuring trustedProxies, all connections will fail with "Proxy headers detected from untrusted address".
4.1 SSH into the Fly Machine
fly ssh console -a your-app-name
4.2 Update moltbot.json with trustedProxies
node -e "
const fs = require('fs');
const configPath = '/data/moltbot.json';
let config = {};
try { config = JSON.parse(fs.readFileSync(configPath)); } catch {}
// Ensure gateway config exists
config.gateway = config.gateway || {};
// Add trusted proxies - MUST include the exact Fly proxy IP
// Check your logs for the actual proxy IP (e.g., 172.16.3.162)
config.gateway.trustedProxies = [
'172.16.0.0/12',
'10.0.0.0/8',
'172.16.3.162' // Add the specific IP from your logs
];
config.gateway.mode = 'local';
config.gateway.bind = 'lan';
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
console.log('Config updated with trustedProxies');
"
4.3 Verify Config
cat /data/moltbot.json | grep -A5 trustedProxies
4.4 Exit and Restart
exit
fly machines restart -a your-app-name
Wait ~60 seconds for the gateway to fully start.
Phase 5: Device Pairing
When you first access the web UI, you'll see "pairing required" errors. This is because Moltbot requires device authentication for security.
5.1 Access the Web UI
Open: https://your-app-name.fly.dev/
You'll see connection errors - this is expected before pairing.
5.2 Check Pending Pairings
fly ssh console -a your-app-name -C "cat /data/devices/pending.json"
You should see a pending pairing request with a requestId and deviceId.
5.3 Approve the Pairing
fly ssh console -a your-app-name -C 'node -e "
const fs = require(\"fs\");
const pending = JSON.parse(fs.readFileSync(\"/data/devices/pending.json\"));
const paired = JSON.parse(fs.readFileSync(\"/data/devices/paired.json\") || \"{}\");
const requestId = Object.keys(pending)[0];
if (requestId) {
const device = pending[requestId];
paired[device.deviceId] = {
deviceId: device.deviceId,
publicKey: device.publicKey,
platform: device.platform,
clientId: device.clientId,
role: device.role,
roles: device.roles,
scopes: device.scopes,
approvedAt: Date.now(),
approvedBy: \"cli\"
};
delete pending[requestId];
fs.writeFileSync(\"/data/devices/pending.json\", JSON.stringify(pending, null, 2));
fs.writeFileSync(\"/data/devices/paired.json\", JSON.stringify(paired, null, 2));
console.log(\"Approved device:\", device.deviceId);
} else {
console.log(\"No pending devices\");
}
"'
5.4 Verify Connection
Refresh the browser. Check the logs:
fly logs -a your-app-name --no-tail | tail -10
Look for: webchat connected conn=... client=moltbot-control-ui
Phase 6: Verification Checklist
Run through this checklist to verify everything works:
## Deployment Verification
- [ ] Gateway listening: `fly logs -a APP | grep "listening on ws"`
- [ ] No proxy warnings: `fly logs -a APP | grep -v "untrusted address"`
- [ ] Web UI connected: `fly logs -a APP | grep "webchat connected"`
- [ ] Health check: `curl https://APP.fly.dev/__health`
Troubleshooting
"Proxy headers detected from untrusted address"
Cause: trustedProxies not configured or doesn't include the Fly proxy IP.
Fix:
- Check logs for the actual proxy IP:
fly logs -a APP | grep "remote="
- Add that IP to
trustedProxies in /data/moltbot.json
- Restart:
fly machines restart -a APP
"pairing required"
Cause: Device not paired.
Fix:
- Check pending:
fly ssh console -a APP -C "cat /data/devices/pending.json"
- Run the approval script from Phase 5.3
- Refresh the browser
Gateway not listening on port 3000
Cause: Gateway taking too long to start (~60 seconds is normal).
Fix:
- Wait 60-90 seconds after deploy/restart
- Check logs:
fly logs -a APP --no-tail | grep "listening"
- Verify process:
fly ssh console -a APP -C "ps -e | grep moltbot"
Invalid --bind error
Cause: Using invalid bind value (e.g., 0.0.0.0).
Fix: Use --bind lan in fly.toml, not --bind 0.0.0.0.
Valid bind values: loopback, lan, tailnet, auto, custom
Complete moltbot.json Example
{
"meta": {
"lastTouchedVersion": "2026.1.27-beta.1"
},
"auth": {
"profiles": {
"anthropic:default": { "provider": "anthropic", "mode": "token" },
"openai:default": { "provider": "openai", "mode": "token" }
}
},
"agents": {
"defaults": {
"model": {
"primary": "anthropic/claude-sonnet-4-5",
"fallbacks": ["anthropic/claude-sonnet-4-5", "openai/gpt-4o"]
},
"maxConcurrent": 4
},
"list": [{ "id": "main", "default": true }]
},
"bindings": [{ "agentId": "main", "match": { "channel": "discord" } }],
"channels": {
"discord": {
"enabled": true,
"groupPolicy": "allowlist",
"guilds": {
"YOUR_GUILD_ID": {
"requireMention": false,
"channels": { "general": { "allow": true } }
}
}
}
},
"gateway": {
"mode": "local",
"bind": "lan",
"trustedProxies": ["172.16.0.0/12", "10.0.0.0/8", "172.16.3.162"]
},
"plugins": {
"entries": {
"discord": { "enabled": true },
"slack": { "enabled": true }
}
}
}
Quick Reference Commands
fly deploy -a APP
fly logs -a APP --no-tail | tail -50
fly ssh console -a APP
fly machines restart -a APP
fly secrets list -a APP
fly ssh console -a APP -C "printenv CLAWDBOT_GATEWAY_TOKEN"
fly ssh console -a APP -C "cat /data/devices/pending.json"
fly ssh console -a APP -C "cat /data/devices/paired.json"
fly ssh console -a APP -C "cat /data/moltbot.json"
Resources