Skip to main content

Fly.io Deployment

Goal: Clawdbot Gateway running on a Fly.io machine with persistent storage, automatic HTTPS, and Discord/channel access.

What you need

  • flyctl CLI installed
  • Fly.io account (free tier works)
  • Model auth: Anthropic API key (or other provider keys)
  • Channel credentials: Discord bot token, Telegram token, etc.

Beginner quick path

  1. Clone repo → customize fly.toml
  2. Create app + volume → set secrets
  3. Deploy with fly deploy
  4. SSH in to create config or use Control UI

1) Create the Fly app

# Clone the repo
git clone https://github.com/clawdbot/clawdbot.git
cd clawdbot

# Create a new Fly app (pick your own name)
fly apps create my-clawdbot

# Create a persistent volume (1GB is usually enough)
fly volumes create clawdbot_data --size 1 --region iad
Tip: Choose a region close to you. Common options: lhr (London), iad (Virginia), sjc (San Jose).

2) Configure fly.toml

Edit fly.toml to match your app name and requirements:
app = "my-clawdbot"  # 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 = "clawdbot_data"
  destination = "/data"
Key settings:
SettingWhy
--bind lanBinds to 0.0.0.0 so Fly’s proxy can reach the gateway
--allow-unconfiguredStarts without a config file (you’ll create one after)
memory = "2048mb"512MB is too small; 2GB recommended
CLAWDBOT_STATE_DIR = "/data"Persists state on the volume

3) Set secrets

# Required: Gateway token (for non-loopback binding)
fly secrets set CLAWDBOT_GATEWAY_TOKEN=$(openssl rand -hex 32)

# Model provider API keys
fly secrets set ANTHROPIC_API_KEY=sk-ant-...

# Optional: Other providers
fly secrets set OPENAI_API_KEY=sk-...
fly secrets set GOOGLE_API_KEY=...

# Channel tokens
fly secrets set DISCORD_BOT_TOKEN=MTQ...
Notes:
  • Non-loopback binds (--bind lan) require CLAWDBOT_GATEWAY_TOKEN for security.
  • Treat these tokens like passwords.

4) Deploy

fly deploy
First deploy builds the Docker image (~2-3 minutes). Subsequent deploys are faster. After deployment, verify:
fly status
fly logs
You should see:
[gateway] listening on ws://0.0.0.0:3000 (PID xxx)
[discord] logged in to discord as xxx

5) Create config file

SSH into the machine to create a proper config:
fly ssh console
Create the config directory and file:
mkdir -p /data
cat > /data/clawdbot.json << 'EOF'
{
  "agents": {
    "defaults": {
      "model": {
        "primary": "anthropic/claude-opus-4-5",
        "fallbacks": ["anthropic/claude-sonnet-4-5", "openai/gpt-4o"]
      },
      "maxConcurrent": 4
    },
    "list": [
      {
        "id": "main",
        "default": true
      }
    ]
  },
  "auth": {
    "profiles": {
      "anthropic:default": { "mode": "token", "provider": "anthropic" },
      "openai:default": { "mode": "token", "provider": "openai" }
    }
  },
  "bindings": [
    {
      "agentId": "main",
      "match": { "channel": "discord" }
    }
  ],
  "channels": {
    "discord": {
      "enabled": true,
      "groupPolicy": "allowlist",
      "guilds": {
        "YOUR_GUILD_ID": {
          "channels": { "general": { "allow": true } },
          "requireMention": false
        }
      }
    }
  },
  "gateway": {
    "mode": "local",
    "bind": "auto"
  },
  "meta": {
    "lastTouchedVersion": "2026.1.24"
  }
}
EOF
Note: With CLAWDBOT_STATE_DIR=/data, the config path is /data/clawdbot.json. Note: The Discord token can come from either:
  • Environment variable: DISCORD_BOT_TOKEN (recommended for secrets)
  • Config file: channels.discord.token
If using env var, no need to add token to config. The gateway reads DISCORD_BOT_TOKEN automatically. Restart to apply:
exit
fly machine restart <machine-id>

6) Access the Gateway

Control UI

Open in browser:
fly open
Or visit https://my-clawdbot.fly.dev/ Paste your gateway token (the one from CLAWDBOT_GATEWAY_TOKEN) to authenticate.

Logs

fly logs              # Live logs
fly logs --no-tail    # Recent logs

SSH Console

fly ssh console

Troubleshooting

”App is not listening on expected address”

The gateway is binding to 127.0.0.1 instead of 0.0.0.0. Fix: Add --bind lan to your process command in fly.toml.

OOM / Memory Issues

Container keeps restarting or getting killed. Signs: SIGABRT, v8::internal::Runtime_AllocateInYoungGeneration, or silent restarts. Fix: Increase memory in fly.toml:
[[vm]]
  memory = "2048mb"
Or update an existing machine:
fly machine update <machine-id> --vm-memory 2048 -y
Note: 512MB is too small. 1GB may work but can OOM under load or with verbose logging. 2GB is recommended.

Gateway Lock Issues

Gateway refuses to start with “already running” errors. This happens when the container restarts but the PID lock file persists on the volume. Fix: Delete the lock file:
fly ssh console --command "rm -f /data/gateway.*.lock"
fly machine restart <machine-id>
The lock file is at /data/gateway.*.lock (not in a subdirectory).

Config Not Being Read

If using --allow-unconfigured, the gateway creates a minimal config. Your custom config at /data/.clawdbot/clawdbot.json should be read on restart. Verify the config exists:
fly ssh console --command "cat /data/.clawdbot/clawdbot.json"

Writing Config via SSH

The fly ssh console -C command doesn’t support shell redirection. To write a config file:
# Use echo + tee (pipe from local to remote)
echo '{"your":"config"}' | fly ssh console -C "tee /data/.clawdbot/clawdbot.json"

# Or use sftp
fly sftp shell
> put /local/path/config.json /data/.clawdbot/clawdbot.json
Note: fly sftp may fail if the file already exists. Delete first:
fly ssh console --command "rm /data/.clawdbot/clawdbot.json"

Updates

# Pull latest changes
git pull

# Redeploy
fly deploy

# Check health
fly status
fly logs

Updating Machine Command

If you need to change the startup command without a full redeploy:
# Get machine ID
fly machines list

# Update command
fly machine update <machine-id> --command "node dist/index.js gateway --port 3000 --bind lan" -y

# Or with memory increase
fly machine update <machine-id> --vm-memory 2048 --command "node dist/index.js gateway --port 3000 --bind lan" -y
Note: After fly deploy, the machine command may reset to what’s in fly.toml. If you made manual changes, re-apply them after deploy.

Notes

  • Fly.io uses x86 architecture (not ARM)
  • The Dockerfile is compatible with both architectures
  • For WhatsApp/Telegram onboarding, use fly ssh console
  • Persistent data lives on the volume at /data

Cost

With the recommended config (shared-cpu-2x, 2GB RAM):
  • ~$10-15/month depending on usage
  • Free tier includes some allowance
See Fly.io pricing for details.