Deploy trên Fly.io

Mục tiêu: Chạy OpenClaw Gateway trên máy Fly.io với lưu trữ bền vững, HTTPS tự động, và kết nối Discord/channel.

Những thứ các bạn cần

  • flyctl CLI đã cài đặt
  • Tài khoản Fly.io (free tier cũng được)
  • Xác thực model: Anthropic API key (hoặc key của provider khác)
  • Thông tin đăng nhập channel: Discord bot token, Telegram token, v.v.

Hướng dẫn nhanh cho người mới

  1. Clone repo → tùy chỉnh fly.toml
  2. Tạo app + volume → thiết lập secrets
  3. Deploy bằng fly deploy
  4. SSH vào để tạo config hoặc dùng Control UI

1) Tạo Fly app

# Clone repo về
git clone https://github.com/openclaw/openclaw.git
cd openclaw

# Tạo Fly app mới (đặt tên tùy thích)
fly apps create my-openclaw

# Tạo volume lưu trữ bền vững (1GB thường là đủ)
fly volumes create openclaw_data --size 1 --region iad

Mẹo: Chọn region gần các bạn nhất. Các lựa chọn phổ biến: lhr (London), iad (Virginia), sjc (San Jose).

2) Cấu hình fly.toml

Sửa file fly.toml để khớp với tên app và yêu cầu của các bạn.

Lưu ý bảo mật: Config mặc định sẽ expose một URL công khai. Để deploy an toàn hơn mà không có public IP, xem phần Private Deployment hoặc dùng fly.private.toml.

app = "my-openclaw"  # Tên app của bạn
primary_region = "iad"

[build]
  dockerfile = "Dockerfile"

[env]
  NODE_ENV = "production"
  OPENCLAW_PREFER_PNPM = "1"
  OPENCLAW_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 = "openclaw_data"
  destination = "/data"

Các thiết lập quan trọng:

Thiết lậpTại sao
--bind lanBind tới 0.0.0.0 để proxy của Fly có thể kết nối tới gateway
--allow-unconfiguredKhởi động mà không cần file config (các bạn sẽ tạo sau)
internal_port = 3000Phải khớp với --port 3000 (hoặc OPENCLAW_GATEWAY_PORT) để health check
memory = "2048mb"512MB quá nhỏ; mình khuyên dùng 2GB
OPENCLAW_STATE_DIR = "/data"Lưu trữ state trên volume

3) Thiết lập secrets

# Bắt buộc: Gateway token (cho non-loopback binding)
fly secrets set OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32)

# API keys của model provider
fly secrets set ANTHROPIC_API_KEY=sk-ant-...

# Tùy chọn: Các provider khác
fly secrets set OPENAI_API_KEY=sk-...
fly secrets set GOOGLE_API_KEY=...

# Channel tokens
fly secrets set DISCORD_BOT_TOKEN=MTQ...

Lưu ý:

  • Non-loopback binds (--bind lan) yêu cầu OPENCLAW_GATEWAY_TOKEN để bảo mật.
  • Coi các token này như mật khẩu nhé.
  • Ưu tiên dùng env vars thay vì config file cho tất cả API keys và tokens. Điều này giúp giữ secrets ra khỏi openclaw.json nơi chúng có thể bị expose hoặc log nhầm.

4) Deploy

fly deploy

Lần deploy đầu tiên sẽ build Docker image (~2-3 phút). Các lần sau sẽ nhanh hơn.

Sau khi deploy xong, kiểm tra:

fly status
fly logs

Các bạn sẽ thấy:

[gateway] listening on ws://0.0.0.0:3000 (PID xxx)
[discord] logged in to discord as xxx

5) Tạo file config

SSH vào máy để tạo config đầy đủ:

fly ssh console

Tạo thư mục config và file:

mkdir -p /data
cat > /data/openclaw.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.29"
  }
}
EOF

Lưu ý: Với OPENCLAW_STATE_DIR=/data, đường dẫn config sẽ là /data/openclaw.json.

Lưu ý: Discord token có thể lấy từ:

  • Biến môi trường: DISCORD_BOT_TOKEN (khuyên dùng cho secrets)
  • File config: channels.discord.token

Nếu dùng env var thì không cần thêm token vào config. Gateway sẽ tự đọc DISCORD_BOT_TOKEN.

Restart để áp dụng:

exit
fly machine restart <machine-id>

6) Truy cập Gateway

Control UI

Mở trong browser:

fly open

Hoặc truy cập https://my-openclaw.fly.dev/

Dán gateway token của các bạn (cái từ OPENCLAW_GATEWAY_TOKEN) để xác thực.

Logs

fly logs              # Live logs
fly logs --no-tail    # Logs gần đây

SSH Console

fly ssh console

Troubleshooting

”App is not listening on expected address”

Gateway đang bind tới 127.0.0.1 thay vì 0.0.0.0.

Cách sửa: Thêm --bind lan vào process command trong fly.toml.

Health checks failing / connection refused

Fly không thể kết nối tới gateway trên port đã cấu hình.

Cách sửa: Đảm bảo internal_port khớp với gateway port (set --port 3000 hoặc OPENCLAW_GATEWAY_PORT=3000).

OOM / Memory Issues

Container liên tục restart hoặc bị kill. Dấu hiệu: SIGABRT, v8::internal::Runtime_AllocateInYoungGeneration, hoặc restart im lặng.

Cách sửa: Tăng memory trong fly.toml:

[[vm]]
  memory = "2048mb"

Hoặc update máy đang chạy:

fly machine update <machine-id> --vm-memory 2048 -y

Lưu ý: 512MB quá nhỏ. 1GB có thể chạy được nhưng dễ bị OOM khi load cao hoặc logging nhiều. Mình khuyên dùng 2GB.

Gateway Lock Issues

Gateway từ chối khởi động với lỗi “already running”.

Điều này xảy ra khi container restart nhưng PID lock file vẫn còn trên volume.

Cách sửa: Xóa lock file:

fly ssh console --command "rm -f /data/gateway.*.lock"
fly machine restart <machine-id>

Lock file nằm ở /data/gateway.*.lock (không phải trong thư mục con).

Config Not Being Read

Nếu dùng --allow-unconfigured, gateway sẽ tạo một config tối thiểu. Config tùy chỉnh của các bạn ở /data/openclaw.json sẽ được đọc khi restart.

Kiểm tra config có tồn tại không:

fly ssh console --command "cat /data/openclaw.json"

Writing Config via SSH

Lệnh fly ssh console -C không hỗ trợ shell redirection. Để ghi config file:

# Dùng echo + tee (pipe từ local sang remote)
echo '{"your":"config"}' | fly ssh console -C "tee /data/openclaw.json"

# Hoặc dùng sftp
fly sftp shell
> put /local/path/config.json /data/openclaw.json

Lưu ý: fly sftp có thể fail nếu file đã tồn tại. Xóa trước:

fly ssh console --command "rm /data/openclaw.json"

State Not Persisting

Nếu các bạn mất credentials hoặc sessions sau khi restart, state dir đang ghi vào filesystem của container.

Cách sửa: Đảm bảo OPENCLAW_STATE_DIR=/data được set trong fly.toml và redeploy.

Updates

# Pull các thay đổi mới nhất
git pull

# Redeploy
fly deploy

# Kiểm tra health
fly status
fly logs

Updating Machine Command

Nếu các bạn cần thay đổi startup command mà không cần redeploy toàn bộ:

# Lấy machine ID
fly machines list

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

# Hoặc kèm tăng memory
fly machine update <machine-id> --vm-memory 2048 --command "node dist/index.js gateway --port 3000 --bind lan" -y

Lưu ý: Sau fly deploy, machine command có thể reset về giá trị trong fly.toml. Nếu các bạn đã thay đổi thủ công, hãy áp dụng lại sau khi deploy.

Private Deployment (Hardened)

Mặc định, Fly cấp phát public IPs, khiến gateway của các bạn có thể truy cập tại https://your-app.fly.dev. Điều này tiện lợi nhưng có nghĩa là deployment của các bạn có thể bị phát hiện bởi các internet scanners (Shodan, Censys, v.v.).

Để có deployment an toàn hơn không có public exposure, dùng private template.

Khi nào dùng private deployment

  • Các bạn chỉ thực hiện các cuộc gọi/tin nhắn outbound (không có inbound webhooks)
  • Các bạn dùng ngrok hoặc Tailscale tunnels cho bất kỳ webhook callbacks nào
  • Các bạn truy cập gateway qua SSH, proxy, hoặc WireGuard thay vì browser
  • Các bạn muốn deployment ẩn khỏi internet scanners

Setup

Dùng fly.private.toml thay vì config tiêu chuẩn:

# Deploy với private config
fly deploy -c fly.private.toml

Hoặc chuyển đổi deployment hiện tại:

# Liệt kê các IPs hiện tại
fly ips list -a my-openclaw

# Release public IPs
fly ips release <public-ipv4> -a my-openclaw
fly ips release <public-ipv6> -a my-openclaw

# Chuyển sang private config để các lần deploy sau không cấp phát lại public IPs
# (xóa [http_service] hoặc deploy với private template)
fly deploy -c fly.private.toml

# Cấp phát private-only IPv6
fly ips allocate-v6 --private -a my-openclaw

Sau đó, fly ips list sẽ chỉ hiển thị IP loại private:

VERSION  IP                   TYPE             REGION
v6       fdaa:x:x:x:x::x      private          global

Accessing a private deployment

Vì không có public URL, dùng một trong các cách sau:

Option 1: Local proxy (đơn giản nhất)

# Forward local port 3000 tới app
fly proxy 3000:3000 -a my-openclaw

# Sau đó mở http://localhost:3000 trong browser

Option 2: WireGuard VPN

# Tạo WireGuard config (một lần)
fly wireguard create

# Import vào WireGuard client, sau đó truy cập qua internal IPv6
# Ví dụ: http://[fdaa:x:x:x:x::x]:3000

Option 3: SSH only

fly ssh console -a my-openclaw

Webhooks with private deployment

Nếu các bạn cần webhook callbacks (Twilio, Telnyx, v.v.) mà không muốn public exposure:

  1. ngrok tunnel - Chạy ngrok bên trong container hoặc như một sidecar
  2. Tailscale Funnel - Expose các paths cụ thể qua Tailscale
  3. Outbound-only - Một số providers (Twilio) hoạt động tốt cho outbound calls mà không cần webhooks

Ví dụ voice-call config với ngrok:

{
  "plugins": {
    "entries": {
      "voice-call": {
        "enabled": true,
        "config": {
          "provider": "twilio",
          "tunnel": { "provider": "ngrok" }
        }
      }
    }
  }
}

Ngrok tunnel chạy bên trong container và cung cấp public webhook URL mà không expose Fly app.

Security benefits

Khía cạnhPublicPrivate
Internet scannersCó thể phát hiệnẨn
Direct attacksCó thểBị chặn
Control UI accessBrowserProxy/VPN
Webhook deliveryTrực tiếpQua tunnel

Notes

  • Fly.io dùng x86 architecture (không phải ARM)
  • Dockerfile tương thích với cả hai kiến trúc
  • Để onboard WhatsApp/Telegram, dùng fly ssh console
  • Dữ liệu bền vững được lưu trên volume tại /data
  • Signal yêu cầu Java + signal-cli; dùng custom image và giữ memory ở 2GB+.

Cost

Với config mình khuyên dùng (shared-cpu-2x, 2GB RAM):

  • ~$10-15/tháng tùy vào mức sử dụng
  • Free tier bao gồm một số allowance

Xem Fly.io pricing để biết chi tiết.