Goal: CoderClaw Gateway running on a Fly.io machine with persistent storage, automatic HTTPS, and Discord/channel access.
fly.tomlfly deploy# Clone the repo
git clone https://github.com/SeanHogg/coderClaw.git
cd coderClaw
# Create a new Fly app (pick your own name)
fly apps create my-coderclaw
# Create a persistent volume (1GB is usually enough)
fly volumes create coderclaw_data --size 1 --region iad
Tip: Choose a region close to you. Common options: lhr (London), iad (Virginia), sjc (San Jose).
Edit fly.toml to match your app name and requirements.
Security note: The default config exposes a public URL. For a hardened deployment with no public IP, see Private Deployment or use fly.private.toml.
app = "my-coderclaw" # Your app name
primary_region = "iad"
[build]
dockerfile = "Dockerfile"
[env]
NODE_ENV = "production"
CODERCLAW_PREFER_PNPM = "1"
CODERCLAW_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 = "coderclaw_data"
destination = "/data"
Key settings:
| Setting | Why |
|---|---|
--bind lan |
Binds to 0.0.0.0 so Fly’s proxy can reach the gateway |
--allow-unconfigured |
Starts without a config file (you’ll create one after) |
internal_port = 3000 |
Must match --port 3000 (or CODERCLAW_GATEWAY_PORT) for Fly health checks |
memory = "2048mb" |
512MB is too small; 2GB recommended |
CODERCLAW_STATE_DIR = "/data" |
Persists state on the volume |
# Required: Gateway token (for non-loopback binding)
fly secrets set CODERCLAW_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:
--bind lan) require CODERCLAW_GATEWAY_TOKEN for security.coderclaw.json where they could be accidentally exposed or logged.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
SSH into the machine to create a proper config:
fly ssh console
Create the config directory and file:
mkdir -p /data
cat > /data/coderclaw.json << 'EOF'
{
"agents": {
"defaults": {
"model": {
"primary": "anthropic/claude-opus-4-6",
"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.29"
}
}
EOF
Note: With CODERCLAW_STATE_DIR=/data, the config path is /data/coderclaw.json.
Note: The Discord token can come from either:
DISCORD_BOT_TOKEN (recommended for secrets)channels.discord.tokenIf 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>
Open in browser:
fly open
Or visit https://my-coderclaw.fly.dev/
Paste your gateway token (the one from CODERCLAW_GATEWAY_TOKEN) to authenticate.
fly logs # Live logs
fly logs --no-tail # Recent logs
fly ssh console
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.
Fly can’t reach the gateway on the configured port.
Fix: Ensure internal_port matches the gateway port (set --port 3000 or CODERCLAW_GATEWAY_PORT=3000).
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 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).
If using --allow-unconfigured, the gateway creates a minimal config. Your custom config at /data/coderclaw.json should be read on restart.
Verify the config exists:
fly ssh console --command "cat /data/coderclaw.json"
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/coderclaw.json"
# Or use sftp
fly sftp shell
> put /local/path/config.json /data/coderclaw.json
Note: fly sftp may fail if the file already exists. Delete first:
fly ssh console --command "rm /data/coderclaw.json"
If you lose credentials or sessions after a restart, the state dir is writing to the container filesystem.
Fix: Ensure CODERCLAW_STATE_DIR=/data is set in fly.toml and redeploy.
# Pull latest changes
git pull
# Redeploy
fly deploy
# Check health
fly status
fly logs
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.
By default, Fly allocates public IPs, making your gateway accessible at https://your-app.fly.dev. This is convenient but means your deployment is discoverable by internet scanners (Shodan, Censys, etc.).
For a hardened deployment with no public exposure, use the private template.
Use fly.private.toml instead of the standard config:
# Deploy with private config
fly deploy -c fly.private.toml
Or convert an existing deployment:
# List current IPs
fly ips list -a my-coderclaw
# Release public IPs
fly ips release <public-ipv4> -a my-coderclaw
fly ips release <public-ipv6> -a my-coderclaw
# Switch to private config so future deploys don't re-allocate public IPs
# (remove [http_service] or deploy with the private template)
fly deploy -c fly.private.toml
# Allocate private-only IPv6
fly ips allocate-v6 --private -a my-coderclaw
After this, fly ips list should show only a private type IP:
VERSION IP TYPE REGION
v6 fdaa:x:x:x:x::x private global
Since there’s no public URL, use one of these methods:
Option 1: Local proxy (simplest)
# Forward local port 3000 to the app
fly proxy 3000:3000 -a my-coderclaw
# Then open http://localhost:3000 in browser
Option 2: WireGuard VPN
# Create WireGuard config (one-time)
fly wireguard create
# Import to WireGuard client, then access via internal IPv6
# Example: http://[fdaa:x:x:x:x::x]:3000
Option 3: SSH only
fly ssh console -a my-coderclaw
If you need webhook callbacks (Twilio, Telnyx, etc.) without public exposure:
Example voice-call config with ngrok:
{
"plugins": {
"entries": {
"voice-call": {
"enabled": true,
"config": {
"provider": "twilio",
"tunnel": { "provider": "ngrok" },
"webhookSecurity": {
"allowedHosts": ["example.ngrok.app"]
}
}
}
}
}
}
The ngrok tunnel runs inside the container and provides a public webhook URL without exposing the Fly app itself. Set webhookSecurity.allowedHosts to the public tunnel hostname so forwarded host headers are accepted.
| Aspect | Public | Private |
|---|---|---|
| Internet scanners | Discoverable | Hidden |
| Direct attacks | Possible | Blocked |
| Control UI access | Browser | Proxy/VPN |
| Webhook delivery | Direct | Via tunnel |
fly ssh console/dataWith the recommended config (shared-cpu-2x, 2GB RAM):
See Fly.io pricing for details.