# Deploy Moltbot To Fly

> Deploy Moltbot to Fly.io

- Skill: `kernel8901/deploy-moltbot-to-fly` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add kernel8901/deploy-moltbot-to-fly`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kernel8901/deploy-moltbot-to-fly/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: kernel8901 (https://skillmd.com/u/kernel8901)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kernel8901/deploy-moltbot-to-fly

---

# 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:
1. Setting up the Fly app with a persistent volume
2. Configuring environment secrets (API keys, gateway token)
3. Creating a proper config file with token authentication
4. Approving device pairing for web UI access

## Prerequisites

Before starting:
- Fly.io CLI installed (`brew install flyctl` or `curl -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

```bash
git clone https://github.com/clawdbot/clawdbot.git moltbot-deploy
cd moltbot-deploy
```

### 1.2 Generate Gateway Token

Generate a secure token for authentication:

```bash
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:

```toml
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 gateway
- `http_service` - Newer Fly format (not `[[services]]`)
- `memory = '2048mb'` - 512MB is too small; 2GB recommended

### 2.2 Create App and Volume

```bash
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` - London
- `sjc` - San Jose (US West)

### 2.3 Set Fly Secrets

```bash
# 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:

```bash
fly deploy -a your-app-name
```

First deployment takes ~3-5 minutes (building Docker image).

**Wait for gateway to start:**
```bash
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

```bash
fly ssh console -a your-app-name
```

### 4.2 Create the config file

```bash
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

```bash
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:

```bash
nslookup your-app-name.fly.dev 8.8.8.8
```

If DNS isn't resolving on your machine, flush your DNS cache:

**macOS:**
```bash
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
```

**Linux:**
```bash
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:

```bash
fly ssh console -a your-app-name
```

Then run:

```bash
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:

```bash
# 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:

```bash
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:** 
1. Wait 2-5 minutes for DNS propagation
2. Use Google DNS: `8.8.8.8` or `1.1.1.1`
3. Flush local DNS cache (see Phase 5.1)

### Config Validation Errors

**Symptoms:** Gateway exits with "Invalid input" or validation errors

**Fix:** Check config syntax:

```bash
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):**

```bash
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:

```bash
fly ssh console -a your-app-name
```

```bash
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

```bash
# 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:

```bash
cd moltbot-deploy
git pull
fly deploy -a your-app-name
```

Config and paired devices persist on the volume across updates.

## Key Lessons

1. **`CLAWDBOT_STATE_DIR=/data`** is critical - without it, config location is wrong
2. **Token must be in BOTH** env var AND config file
3. **Use `http_service`** not `[[services]]` (newer Fly format)
4. **Device pairing is required** even with token auth
5. **DNS takes time** - wait 2-5 minutes, flush cache if needed
6. **Fresh deploy is often faster** than debugging corrupted state
7. **2GB RAM minimum** - 512MB will OOM, 1GB may work but 2GB is recommended

## Resources

- [Fly.io Documentation](https://fly.io/docs/)
- [Moltbot Official Docs](https://docs.molt.bot/platforms/fly)
- [Clawdbot GitHub](https://github.com/clawdbot/clawdbot)

