Deploy Moltbot to Fly.io
Deploy Moltbot (Clawdbot) to Fly.io with proper configuration, persistent storage, and device pairing.
Overview
Deploying Moltbot to Fly.io requires:
- Setting up the Fly app with a persistent volume
- Configuring environment secrets (API keys, gateway token)
- Creating a proper config file with token authentication
- Approving device pairing for web UI access
Prerequisites
Before starting:
- Fly.io CLI installed (
brew install flyctlorcurl -L https://fly.io/install.sh | sh) - Fly.io account and logged in (
fly auth login) - Anthropic API key (and optionally OpenAI API key)
- Git installed
Phase 1: Clone and Setup
1.1 Clone the Moltbot Repository
git clone https://github.com/clawdbot/clawdbot.git moltbot-deploy
cd moltbot-deploy
1.2 Generate Gateway Token
Generate a secure token for authentication:
openssl rand -hex 32
IMPORTANT: Save this token - you'll need it for:
- Fly secrets
- Config file
- Web UI access URL
Phase 2: Fly.io Configuration
2.1 Create fly.toml
Create fly.toml with the correct configuration:
app = 'your-app-name'
primary_region = 'iad'
[build]
dockerfile = 'Dockerfile'
[env]
NODE_ENV = 'production'
CLAWDBOT_PREFER_PNPM = '1'
CLAWDBOT_STATE_DIR = '/data'
NODE_OPTIONS = '--max-old-space-size=1536'
[processes]
app = "node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = false
auto_start_machines = true
min_machines_running = 1
processes = ["app"]
[[vm]]
size = 'shared-cpu-2x'
memory = '2048mb'
[mounts]
source = 'moltbot_data'
destination = '/data'
CRITICAL Settings:
CLAWDBOT_STATE_DIR = '/data'- Required for proper config persistence--bind lan- Allows Fly's proxy to reach the gatewayhttp_service- Newer Fly format (not[[services]])memory = '2048mb'- 512MB is too small; 2GB recommended
2.2 Create App and Volume
fly apps create your-app-name
fly volumes create moltbot_data --region iad --size 1 -a your-app-name -y
Choose a region close to you:
iad- Virginia (US East)lhr- Londonsjc- San Jose (US West)
2.3 Set Fly Secrets
# Set your generated token
fly secrets set CLAWDBOT_GATEWAY_TOKEN="YOUR-TOKEN-HERE" -a your-app-name
# Set API keys
fly secrets set ANTHROPIC_API_KEY="sk-ant-xxxxx" -a your-app-name
fly secrets set OPENAI_API_KEY="sk-xxxxx" -a your-app-name # Optional
Note: Secrets are deployed on first fly deploy, not immediately.
Phase 3: Deploy
Deploy the application:
fly deploy -a your-app-name
First deployment takes ~3-5 minutes (building Docker image).
Wait for gateway to start:
fly logs -a your-app-name --no-tail | grep "listening on"
You should see:
[gateway] listening on ws://0.0.0.0:3000 (PID xxx)
Phase 4: Create Config File
CRITICAL: The config file must include the same token as the env var for authentication to work.
4.1 SSH into the machine
fly ssh console -a your-app-name
4.2 Create the config file
cat > /data/moltbot.json << 'EOF'
{
"gateway": {
"mode": "local",
"bind": "lan",
"auth": {
"mode": "token",
"token": "YOUR-TOKEN-HERE"
}
},
"agents": {
"defaults": {
"model": {
"primary": "anthropic/claude-opus-4-5"
}
}
},
"auth": {
"profiles": {
"anthropic:default": { "mode": "token", "provider": "anthropic" }
}
}
}
EOF
Replace YOUR-TOKEN-HERE with your actual token!
4.3 Exit and restart
exit
fly machine restart <machine-id> -a your-app-name
Get machine ID with: fly machines list -a your-app-name
Phase 5: Access and Device Pairing
5.1 Wait for DNS propagation
DNS may take 2-5 minutes to propagate. Check status:
nslookup your-app-name.fly.dev 8.8.8.8
If DNS isn't resolving on your machine, flush your DNS cache:
macOS:
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
Linux:
sudo systemd-resolve --flush-caches
5.2 Access Web UI
Open in browser with the tokenized URL:
https://your-app-name.fly.dev/?token=YOUR-TOKEN-HERE
You'll see "disconnected (1008): pairing required" - this is normal!
5.3 Approve Device Pairing
While the browser is open and attempting to connect, approve the pairing:
fly ssh console -a your-app-name
Then run:
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 Refresh Browser
After approval, refresh your browser. You should now be connected! ๐
Troubleshooting
Gateway Token Mismatch
Symptoms: unauthorized: gateway token mismatch
Fix: Token in config file must match the env var:
# Check env var token
fly ssh console -a your-app-name -C "printenv CLAWDBOT_GATEWAY_TOKEN"
# Update config file to match
fly ssh console -a your-app-name
# Edit /data/moltbot.json and update gateway.auth.token
App Not Listening / Connection Refused
Symptoms: instance refused connection or not listening on expected address
Fix: Ensure --bind lan in fly.toml and gateway is fully started:
fly logs -a your-app-name --no-tail | tail -50
Wait 30-60 seconds after deploy for gateway to initialize.
DNS Not Resolving
Symptoms: Could not resolve host
Fix:
- Wait 2-5 minutes for DNS propagation
- Use Google DNS:
8.8.8.8or1.1.1.1 - Flush local DNS cache (see Phase 5.1)
Config Validation Errors
Symptoms: Gateway exits with "Invalid input" or validation errors
Fix: Check config syntax:
fly ssh console -a your-app-name -C "cat /data/moltbot.json"
Common issues:
- Invalid
auth.mode: Only"token"is valid (not"off") - Missing commas in JSON
- Mismatched quotes
State Not Persisting
Symptoms: Config/devices reset after restart
Fix: Ensure CLAWDBOT_STATE_DIR=/data is set in fly.toml [env] section.
Stuck Deployment
Symptoms: Machine keeps restarting or won't stabilize
Nuclear option (fastest):
fly apps destroy your-app-name -y
# Then re-run Phase 2 onwards with fresh setup
Advanced: Trusted Proxies (Optional)
If you see proxy warnings in logs, add trusted proxies:
fly ssh console -a your-app-name
node -e "
const fs = require('fs');
const config = JSON.parse(fs.readFileSync('/data/moltbot.json'));
config.gateway.trustedProxies = [
'172.16.0.0/12',
'10.0.0.0/8'
];
fs.writeFileSync('/data/moltbot.json', JSON.stringify(config, null, 2));
console.log('Trusted proxies configured');
"
Restart machine after changes.
Quick Reference
# Check status
fly status -a APP
# View logs
fly logs -a APP --no-tail | tail -50
# SSH into machine
fly ssh console -a APP
# Restart machine
fly machines list -a APP # Get machine ID
fly machine restart <machine-id> -a APP
# Check secrets
fly secrets list -a APP
# Get gateway token
fly ssh console -a APP -C "printenv CLAWDBOT_GATEWAY_TOKEN"
# Redeploy
fly deploy -a APP
Updates
To update Moltbot:
cd moltbot-deploy
git pull
fly deploy -a your-app-name
Config and paired devices persist on the volume across updates.
Key Lessons
CLAWDBOT_STATE_DIR=/datais critical - without it, config location is wrong- Token must be in BOTH env var AND config file
- Use
http_servicenot[[services]](newer Fly format) - Device pairing is required even with token auth
- DNS takes time - wait 2-5 minutes, flush cache if needed
- Fresh deploy is often faster than debugging corrupted state
- 2GB RAM minimum - 512MB will OOM, 1GB may work but 2GB is recommended