Deploy Moltbot (Clawdbot) to Fly.io with proper proxy configuration, trusted proxies setup, and device pairing
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.
Deploying Moltbot to Fly.io requires:
trustedProxies to allow Fly's internal proxy networkBefore starting:
brew install flyctl or curl -L https://fly.io/install.sh | sh)fly auth login)git clone https://github.com/moltbot/moltbot.git
cd moltbot
Create a .env.example file in the project root with the required environment variables:
cat > .env.example << 'EOF'
# Required: Your Anthropic API key
ANTHROPIC_API_KEY=sk-ant-xxxxx
# Optional: OpenAI API key for fallback models
OPENAI_API_KEY=sk-xxxxx
# Required: Gateway token for authentication (generate with: openssl rand -hex 32)
CLAWDBOT_GATEWAY_TOKEN=your-64-char-hex-token
# Optional: Slack Bot Token (if using Slack)
SLACK_BOT_TOKEN=xoxb-xxxxx
# Optional: Discord Bot Token (if using Discord)
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 keyCLAWDBOT_GATEWAY_TOKEN: Generate with openssl rand -hex 32"openssl rand -hex 32
Save this token - you'll need it for Fly secrets and for web UI authentication.
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.
fly apps create your-app-name
fly volumes create moltbot_data --region iad --size 1
# Required 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
# Optional secrets
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"
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.
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)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".
fly ssh console -a your-app-name
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');
"
cat /data/moltbot.json | grep -A5 trustedProxies
exit
fly machines restart -a your-app-name
Wait ~60 seconds for the gateway to fully start.
When you first access the web UI, you'll see "pairing required" errors. This is because Moltbot requires device authentication for security.
Open: https://your-app-name.fly.dev/
You'll see connection errors - this is expected before pairing.
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.
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\");
}
"'
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
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`
Cause: trustedProxies not configured or doesn't include the Fly proxy IP.
Fix:
fly logs -a APP | grep "remote="trustedProxies in /data/moltbot.jsonfly machines restart -a APPCause: Device not paired.
Fix:
fly ssh console -a APP -C "cat /data/devices/pending.json"Cause: Gateway taking too long to start (~60 seconds is normal).
Fix:
fly logs -a APP --no-tail | grep "listening"fly ssh console -a APP -C "ps -e | grep moltbot"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
{
"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 }
}
}
}
# Deploy
fly deploy -a APP
# Logs
fly logs -a APP --no-tail | tail -50
# SSH
fly ssh console -a APP
# Restart
fly machines restart -a APP
# List secrets
fly secrets list -a APP
# Get gateway token
fly ssh console -a APP -C "printenv CLAWDBOT_GATEWAY_TOKEN"
# Check pending devices
fly ssh console -a APP -C "cat /data/devices/pending.json"
# Check paired devices
fly ssh console -a APP -C "cat /data/devices/paired.json"
# Check config
fly ssh console -a APP -C "cat /data/moltbot.json"