Fly.io Deployment

Ziel: OpenClaw Gateway läuft auf einer Fly.io Machine mit persistentem Speicher, automatischem HTTPS und Discord/Channel-Zugriff.

Was du brauchst

  • flyctl CLI installiert
  • Fly.io Account (Free Tier reicht)
  • Model Auth: Anthropic API Key (oder andere Provider Keys)
  • Channel Credentials: Discord Bot Token, Telegram Token, etc.

Schnelleinstieg für Anfänger

  1. Repo clonen → fly.toml anpassen
  2. App + Volume erstellen → Secrets setzen
  3. Mit fly deploy deployen
  4. Per SSH Config erstellen oder Control UI nutzen

1) Fly App erstellen

# Repo clonen
git clone https://github.com/openclaw/openclaw.git
cd openclaw

# Neue Fly App erstellen (wähle deinen eigenen Namen)
fly apps create my-openclaw

# Persistentes Volume erstellen (1GB reicht normalerweise)
fly volumes create openclaw_data --size 1 --region iad

Tipp: Wähle eine Region in deiner Nähe. Gängige Optionen: lhr (London), iad (Virginia), sjc (San Jose).

2) fly.toml konfigurieren

Bearbeite fly.toml und passe App-Name und Anforderungen an.

Sicherheitshinweis: Die Standard-Config macht dein Gateway öffentlich erreichbar. Für ein abgesichertes Deployment ohne öffentliche IP siehe Private Deployment oder nutze fly.private.toml.

app = "my-openclaw"  # Dein App-Name
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"

Wichtige Einstellungen:

EinstellungWarum
--bind lanBindet an 0.0.0.0, damit Fly’s Proxy das Gateway erreichen kann
--allow-unconfiguredStartet ohne Config-Datei (die erstellst du später)
internal_port = 3000Muss mit --port 3000 (oder OPENCLAW_GATEWAY_PORT) für Fly Health Checks passen
memory = "2048mb"512MB ist zu wenig; 2GB empfohlen
OPENCLAW_STATE_DIR = "/data"Speichert State persistent auf dem Volume

3) Secrets setzen

# Erforderlich: Gateway Token (für Non-Loopback Binding)
fly secrets set OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32)

# Model Provider API Keys
fly secrets set ANTHROPIC_API_KEY=sk-ant-...

# Optional: Andere Provider
fly secrets set OPENAI_API_KEY=sk-...
fly secrets set GOOGLE_API_KEY=...

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

Hinweise:

  • Non-Loopback Binds (--bind lan) benötigen OPENCLAW_GATEWAY_TOKEN aus Sicherheitsgründen.
  • Behandle diese Tokens wie Passwörter.
  • Nutze Umgebungsvariablen statt Config-Datei für alle API Keys und Tokens. So bleiben Secrets aus openclaw.json raus, wo sie versehentlich exponiert oder geloggt werden könnten.

4) Deployen

fly deploy

Beim ersten Deploy wird das Docker Image gebaut (~2-3 Minuten). Spätere Deploys gehen schneller.

Nach dem Deployment prüfen:

fly status
fly logs

Du solltest sehen:

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

5) Config-Datei erstellen

Per SSH auf die Machine verbinden und eine richtige Config erstellen:

fly ssh console

Config-Verzeichnis und -Datei erstellen:

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

Hinweis: Mit OPENCLAW_STATE_DIR=/data liegt die Config unter /data/openclaw.json.

Hinweis: Der Discord Token kann aus zwei Quellen kommen:

  • Umgebungsvariable: DISCORD_BOT_TOKEN (empfohlen für Secrets)
  • Config-Datei: channels.discord.token

Wenn du die Umgebungsvariable nutzt, brauchst du den Token nicht in die Config einzutragen. Das Gateway liest DISCORD_BOT_TOKEN automatisch.

Neustart zum Anwenden:

exit
fly machine restart <machine-id>

6) Gateway aufrufen

Control UI

Im Browser öffnen:

fly open

Oder besuche https://my-openclaw.fly.dev/

Füge deinen Gateway Token (den aus OPENCLAW_GATEWAY_TOKEN) ein, um dich zu authentifizieren.

Logs

fly logs              # Live Logs
fly logs --no-tail    # Letzte Logs

SSH Console

fly ssh console

Troubleshooting

”App is not listening on expected address”

Das Gateway bindet an 127.0.0.1 statt an 0.0.0.0.

Fix: Füge --bind lan zum Process-Command in fly.toml hinzu.

Health Checks schlagen fehl / Connection refused

Fly kann das Gateway auf dem konfigurierten Port nicht erreichen.

Fix: Stelle sicher, dass internal_port mit dem Gateway Port übereinstimmt (setze --port 3000 oder OPENCLAW_GATEWAY_PORT=3000).

OOM / Memory Issues

Container startet ständig neu oder wird gekillt. Anzeichen: SIGABRT, v8::internal::Runtime_AllocateInYoungGeneration, oder stille Neustarts.

Fix: Erhöhe den Speicher in fly.toml:

[[vm]]
  memory = "2048mb"

Oder update eine bestehende Machine:

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

Hinweis: 512MB ist zu wenig. 1GB kann funktionieren, aber bei Last oder verbose Logging zu OOM führen. 2GB ist empfohlen.

Gateway Lock Issues

Gateway verweigert den Start mit “already running” Fehlern.

Das passiert, wenn der Container neu startet, aber die PID Lock-Datei auf dem Volume bestehen bleibt.

Fix: Lösche die Lock-Datei:

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

Die Lock-Datei liegt unter /data/gateway.*.lock (nicht in einem Unterverzeichnis).

Config wird nicht gelesen

Bei --allow-unconfigured erstellt das Gateway eine minimale Config. Deine eigene Config unter /data/openclaw.json sollte beim Neustart gelesen werden.

Prüfe, ob die Config existiert:

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

Config per SSH schreiben

Der Befehl fly ssh console -C unterstützt keine Shell-Redirection. Um eine Config-Datei zu schreiben:

# echo + tee nutzen (pipe von lokal zu remote)
echo '{"your":"config"}' | fly ssh console -C "tee /data/openclaw.json"

# Oder sftp nutzen
fly sftp shell
> put /local/path/config.json /data/openclaw.json

Hinweis: fly sftp kann fehlschlagen, wenn die Datei bereits existiert. Erst löschen:

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

State wird nicht persistent gespeichert

Wenn du Credentials oder Sessions nach einem Neustart verlierst, schreibt das State-Verzeichnis ins Container-Dateisystem.

Fix: Stelle sicher, dass OPENCLAW_STATE_DIR=/data in fly.toml gesetzt ist und deploye neu.

Updates

# Neueste Änderungen pullen
git pull

# Neu deployen
fly deploy

# Health checken
fly status
fly logs

Machine Command updaten

Wenn du den Startup-Command ohne vollständiges Redeploy ändern musst:

# Machine ID holen
fly machines list

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

# Oder mit Memory-Erhöhung
fly machine update <machine-id> --vm-memory 2048 --command "node dist/index.js gateway --port 3000 --bind lan" -y

Hinweis: Nach fly deploy kann der Machine Command auf das zurückgesetzt werden, was in fly.toml steht. Wenn du manuelle Änderungen gemacht hast, wende sie nach dem Deploy erneut an.

Private Deployment (Hardened)

Standardmäßig vergibt Fly öffentliche IPs, wodurch dein Gateway unter https://your-app.fly.dev erreichbar ist. Das ist praktisch, bedeutet aber auch, dass dein Deployment von Internet-Scannern (Shodan, Censys, etc.) gefunden werden kann.

Für ein abgesichertes Deployment ohne öffentliche Erreichbarkeit nutze das Private Template.

Wann Private Deployment nutzen

  • Du machst nur ausgehende Calls/Nachrichten (keine eingehenden Webhooks)
  • Du nutzt ngrok oder Tailscale Tunnel für Webhook-Callbacks
  • Du greifst auf das Gateway per SSH, Proxy oder WireGuard statt Browser zu
  • Du willst das Deployment vor Internet-Scannern verstecken

Setup

Nutze fly.private.toml statt der Standard-Config:

# Mit Private Config deployen
fly deploy -c fly.private.toml

Oder konvertiere ein bestehendes Deployment:

# Aktuelle IPs auflisten
fly ips list -a my-openclaw

# Öffentliche IPs freigeben
fly ips release <public-ipv4> -a my-openclaw
fly ips release <public-ipv6> -a my-openclaw

# Zu Private Config wechseln, damit zukünftige Deploys keine öffentlichen IPs neu vergeben
# (entferne [http_service] oder deploye mit dem Private Template)
fly deploy -c fly.private.toml

# Private-Only IPv6 zuweisen
fly ips allocate-v6 --private -a my-openclaw

Danach sollte fly ips list nur noch eine IP vom Typ private zeigen:

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

Private Deployment aufrufen

Da es keine öffentliche URL gibt, nutze eine dieser Methoden:

Option 1: Local Proxy (am einfachsten)

# Lokalen Port 3000 zur App forwarden
fly proxy 3000:3000 -a my-openclaw

# Dann http://localhost:3000 im Browser öffnen

Option 2: WireGuard VPN

# WireGuard Config erstellen (einmalig)
fly wireguard create

# In WireGuard Client importieren, dann per interner IPv6 zugreifen
# Beispiel: http://[fdaa:x:x:x:x::x]:3000

Option 3: Nur SSH

fly ssh console -a my-openclaw

Webhooks mit Private Deployment

Wenn du Webhook-Callbacks (Twilio, Telnyx, etc.) ohne öffentliche Erreichbarkeit brauchst:

  1. ngrok Tunnel - ngrok im Container oder als Sidecar laufen lassen
  2. Tailscale Funnel - Bestimmte Pfade per Tailscale exponieren
  3. Nur ausgehend - Manche Provider (Twilio) funktionieren für ausgehende Calls auch ohne Webhooks

Beispiel Voice-Call Config mit ngrok:

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

Der ngrok Tunnel läuft im Container und stellt eine öffentliche Webhook-URL bereit, ohne die Fly App selbst zu exponieren.

Sicherheitsvorteile

AspektPublicPrivate
Internet-ScannerAuffindbarVersteckt
Direkte AngriffeMöglichBlockiert
Control UI ZugriffBrowserProxy/VPN
Webhook-ZustellungDirektVia Tunnel

Hinweise

  • Fly.io nutzt x86 Architektur (nicht ARM)
  • Das Dockerfile ist mit beiden Architekturen kompatibel
  • Für WhatsApp/Telegram Onboarding nutze fly ssh console
  • Persistente Daten liegen auf dem Volume unter /data
  • Signal benötigt Java + signal-cli; nutze ein Custom Image und halte den Speicher bei 2GB+.

Kosten

Mit der empfohlenen Config (shared-cpu-2x, 2GB RAM):

  • ~10-15 $/Monat je nach Nutzung
  • Free Tier beinhaltet ein gewisses Kontingent

Details siehe Fly.io Pricing.