OpenClawをDocker + Ollama + Discordで動かす【ローカルAIエージェント構築】
AIDevOpsLinux

OpenClawをDocker + Ollama + Discordで動かす【ローカルAIエージェント構築】

目次
  1. はじめに
  2. 環境
  3. アーキテクチャ
  4. 前提:Dockerネットワーク
  5. Ollamaの設定
  6. OpenClawのインストール
  7. Discord Botの設定
  8. リモートアクセス
  9. ワークスペースファイル
  10. トラブルシューティング
  11. まとめ

はじめに

前々回でGTTメモリを拡張して、前回でComfyUIによる画像生成ができるようになりました。次にやりたかったのは「Discordから気軽に話しかけられるAIアシスタント」です。

今、特に話題になっているOSSのOpenClaw。自分のマシンで動かすパーソナルAIアシスタントで、Discord、WhatsApp、Telegramなど既存のチャットサービスをフロントエンドとして使えます。しかもLLMのバックエンドにOllamaを指定できるので、APIキー不要・完全ローカルで動かせる。

ただ、Docker環境でOpenClawをOllamaに繋いでDiscordで動かすまでの道のりは、正直かなり長かったです。公式ドキュメントはあるけどDocker特有の罠が多く、特にDiscordのBot接続で何時間もハマりました。この記事では、その試行錯誤をすべてまとめます。

環境

  • マシン: GMKtec NucBox EVO X1(Ubuntu 24.04)

  • LLM: Ollama(ROCm版)+ Qwen3.5:4b

  • Docker network: ai_network(Ollama等の既存コンテナと共有)

アーキテクチャ

全体像はこんな感じです。

Discord ←→ OpenClaw Gateway ←→ Ollama (qwen3.5:4b)
              ↑
         Control UI (port 18789)
         OpenClaw Office (port 5180)

すべてDockerコンテナで動作し、ai_networkという共有ネットワークで相互接続しています。OpenClawのゲートウェイがDiscordとOllamaの間を仲介して、メッセージの受信→LLMへの問い合わせ→応答の返却をやってくれます。

前提:Dockerネットワーク

前回の記事で作成したai_networkを使います。ComfyUIやOllamaなどのコンテナはすべてこのネットワークに参加しており、コンテナ名(ai_ollamaなど)で相互にアクセスできるようになっています。

Ollamaの設定

Ollamaは前回の記事で使ったComfyUIと同じai_networkに参加させます。

docker-compose.yml(親ディレクトリ)

services:
  ollama:
    image: ollama/ollama:rocm
    container_name: ai_ollama
    ports:
      - "11434:11434"
    devices:
      - "/dev/kfd:/dev/kfd"
      - "/dev/dri:/dev/dri"
    volumes:
      - ./data/ollama:/root/.ollama
    environment:
      - OLLAMA_CONTEXT_LENGTH=65536
    restart: always
    networks:
      - ai_network

モデルの選定

OpenClawで使うモデル選びは結構重要です。エージェントとして動かすにはツール呼び出し(Function Calling)に対応している必要があります。

モデル

特徴

ツール呼び出し

所感

qwen3.5:4b

軽量・高速

○ 対応

メインで使用中。レスポンスが速い

qwen3:8b

より高品質な応答

○ 対応

安定版。4bでは物足りないとき用

gemma3:12b

高品質だが重い

× 非対応

ツール呼び出し非対応なのでエージェント用途には不向き

注意: qwen3.5:9bは一見よさそうに見えますが、Ollamaでのツール呼び出しにバグがあります。ツールを実行せずにテキストとして出力してしまう問題が報告されており(ollama#14745)、エージェント用途では不安定です。4bを推奨します。

OpenClawのインストール

ディレクトリ構成

OpenClawはOllamaやComfyUIとは別ディレクトリで管理します。エージェントが暴走したときにOllamaまで巻き添えにしたくないので、docker compose downで単独で落とせるようにしておくのが安全です。

$ mkdir -p ~/local_LLM/openclaw && cd ~/local_LLM/openclaw

セットアップスクリプト

# 公式セットアップスクリプトを使用
$ curl -fsSL https://raw.githubusercontent.com/openclaw/openclaw/main/scripts/docker/setup.sh | bash

セットアップウィザードが起動して色々聞かれます。自分の場合はざっくり以下のように選択しました。

I understand this is personal-by-default ...  → Yes
Setup mode                                    → QuickStart
Model/auth provider                           → Ollama
Ollama base URL                               → http://127.0.0.1:11434
Select channel (QuickStart)                   → Skip for now
Enable hooks                                  → Skip for now

Ollama base URLはウィザード時点ではhttp://127.0.0.1:11434でOKです(ここで入力した値は後からopenclaw.jsonでhttp://ai_ollama:11434に書き換えます)。チャンネルとhooksは後から設定するのでスキップ。

基本的に後からopenclaw.jsonを直接編集する方が確実です。ウィザードはとりあえず通してしまって構いません。

設定ファイルの全体像

OpenClawの設定は複数のファイルに分散しています。最初は混乱するので、どこに何があるか整理しておきます。

ファイル

場所

役割

.env

~/local_LLM/openclaw/.env

環境変数(トークン、イメージ名など)

docker-compose.yml

~/local_LLM/openclaw/docker-compose.yml

Dockerコンテナの定義

openclaw.json

~/.openclaw/openclaw.json

OpenClawの本体設定(モデル、チャンネル、エージェント)

ワークスペースファイル

~/.openclaw/agents/main/

エージェントの人格定義(SOUL.md等)

.envdocker-compose.ymlはOpenClawのディレクトリに、openclaw.jsonとワークスペースはホームディレクトリの~/.openclaw/に配置されます。この分離がちょっとわかりにくい。

.envの設定

OPENCLAW_IMAGE=ghcr.io/openclaw/openclaw:main
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_CONFIG_DIR=~/.openclaw
OPENCLAW_WORKSPACE_DIR=~/openclaw/workspace
OPENCLAW_GATEWAY_TOKEN=(自動生成されたトークン)
DISCORD_BOT_TOKEN=(Discord Developer Portalで取得したトークン)

openclaw.json

~/.openclaw/openclaw.jsonにOllamaの接続先とDiscordの設定を記述します。

DiscordのサーバーIDなどは以下で取得してください。

  1. Discordの設定 → 詳細設定 → 開発者モードをON

  2. サーバーアイコンを右クリック → 「IDをコピー」でサーバーIDを取得

  3. 自分のアイコンを右クリック → 「IDをコピー」でユーザーIDを取得

  4. テキストチャンネルを右クリック → 「IDをコピー」でチャンネルIDを取得

{
  "models": {
    "providers": {
      "ollama": {
        "baseUrl": "http://ai_ollama:11434",
        "apiKey": "ollama-local",
        "models": [{
          "id": "qwen3.5:4b",
          "name": "qwen3.5 4B",
          "reasoning": false,
          "input": ["text"],
          "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
          "contextWindow": 131072,
          "maxTokens": 8192
        }]
      }
    }
  },
  "agents": {
    "defaults": {
      "model": {"primary": "ollama/qwen3.5:4b"}
    }
  },
  "channels": {
    "discord": {
      "enabled": true,
      "groupPolicy": "allowlist",
      "guilds": {
        "ここにサーバーID": {
          "requireMention": true,
          "users": ["ここにユーザーID"],
          "channels": {
            "ここに専用チャンネルID": {"requireMention": false},
            "*": {"allow": true}
          }
        }
      }
    }
  }
}

ポイントはbaseUrlの部分。localhostではなくai_ollama(Dockerコンテナ名)を指定します。同じai_networkに参加していればコンテナ名でアクセスできます。

docker-compose.yml

services:
  openclaw-gateway:
    image: ${OPENCLAW_IMAGE:-openclaw:local}
    environment:
      HOME: /home/node
      TERM: xterm-256color
      OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN:-}
      TZ: ${OPENCLAW_TZ:-UTC}
      DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN:-}
    volumes:
      - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
      - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
    ports:
      - "18789:18789"
      - "5180:5180"
    init: true
    restart: unless-stopped
    networks:
      - default
      - ai_network
    command:
      ["node", "dist/index.js", "gateway", "--bind", "lan", "--port", "18789"]

networks:
  default:
  ai_network:
    external: true

Discord Botの設定

ここからがハマりどころの本番です。

1. Discord Developer Portalでの作成

  1. Discord Developer Portalでアプリケーションを作成

  2. 左メニューの「Bot」をクリック

  3. 以下のPrivileged Gateway Intentsを3つともONにする

    • Presence Intent

    • Server Members Intent

    • Message Content Intent(これが一番重要)

  4. 「Reset Token」をクリックしてBotトークンをコピー → .envDISCORD_BOT_TOKENに設定

  5. 左メニューの「OAuth2」→「URL Generator」でbotを招待するURLを生成

    • Scopesでbotapplications.commandsにチェック

    • Bot Permissionsで「Send Messages」「Read Message History」等にチェック

  6. 生成されたURLをブラウザで開いて、自分のサーバーにBotを招待

2. awaiting gateway readinessで止まる問題

ここが一番ハマったポイントです。セットアップ通りにやったはずなのに、ゲートウェイのログが延々とawaiting gateway readinessのまま進まない。

[discord] client initialized as 1485647598155989212 (0penClaw); awaiting gateway readiness

原因はDiscord Bot Tokenの渡し方でした。

最初はopenclaw.jsonの中に直接トークンを書いていたんですが、これだとDiscordのREADYイベントが来ない。docker-compose.ymlのenvironmentセクションにDISCORD_BOT_TOKENを設定することで解決しました。

environment:
  DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN:-}

.envファイルにトークンを書いて、docker-compose.ymlで環境変数として渡す。この形じゃないとダメでした。openclaw.jsonのchannels.discordにはtokenを書かず、"enabled": trueだけにしてください。

3. ペアリングの承認

BotがDiscordに接続できたら、次はペアリングです。BotにDMを送ると、ペアリングリクエストが発生します。

まずDiscordのプライバシー設定で「ダイレクトメッセージ」を有効にしておいてください(サーバーアイコン右クリック → プライバシー設定)。

BotにDMで「こんにちは」と送ったら、以下のメッセージが返ってくるで承認します。

# ペアリングリクエストの確認
$ docker compose exec openclaw-gateway node dist/index.js devices list

# 承認(リストに表示されたコードを使用)
$ docker compose exec openclaw-gateway node dist/index.js pairing approve discord <コード>

ペアリングが完了するとこんな一言が・・・冗談でもマジで怖っす。

🦞 OpenClaw 2026.3.23 (unknown) — Your .env is showing; don't worry, I'll pretend I didn't see it.

注意: docker compose run --rm openclaw-cli devices approveはDockerネットワークの問題でエラーになることがあります。docker compose exec openclaw-gateway node dist/index.jsを直接使う方が確実です。

4. チャンネル設定の使い分け

  • 専用チャンネル: "requireMention": false — メンションなしで全メッセージに反応

  • その他のチャンネル: "*": {"allow": true} + "requireMention": true — @メンションしたときだけ反応

個人サーバーなら専用チャンネルを1つ作ってrequireMention: falseにしておくと、普通にチャットするだけでエージェントが答えてくれるので便利です。

リモートアクセス

ゲートウェイのControl UIはlocalhostからのアクセスしか受け付けないセキュリティ設定になっています(device identity必須)。別PCからアクセスする場合はSSHポートフォワードを使います。

$ ssh -N -L 18789:127.0.0.1:18789 -L 5180:127.0.0.1:5180 user@[サーバのIPアドレス]

Control UIのトークン付きURLは以下で取得できます。

$ docker compose exec openclaw-gateway node dist/index.js dashboard --no-open

表示されたhttp://127.0.0.1:18789/#token=...のURLをブラウザに貼り付けてアクセスしてください。

  • http://localhost:18789 → Control UI(セッション詳細、ツール呼び出しのログ、エラー確認)

  • http://localhost:5180 → OpenClaw Office(エージェント活動のビジュアル監視)

ワークスペースファイル

エージェントの性格や知識を定義するマークダウンファイルを~/.openclaw/agents/main/に配置します。これがエージェントの「人格」になります。

ファイル

用途

SOUL.md

エージェントの性格・口調・振る舞い

IDENTITY.md

名前・役割の定義

USER.md

ユーザー(自分)の情報

TOOLS.md

使用可能なツールの説明

BOOTSTRAP.md

初回起動時の確認タスク

BOOTSTRAP.mdには「今日の日付は?」「自己紹介して」のような簡単なタスクを入れておくと、エージェントが正しく動作しているか確認できます。Discordで「BOOTSTRAP.mdを読んで」と送れば実行してくれます。

注意: 「今のDockerコンテナの状況を教えて」のようなタスクは入れないでください。エージェントにはDockerへのアクセス権がないので、延々とツールを呼び出そうとしてサーバーに負荷がかかります。実際にこれをやってしまい、CPUが張り付いて慌ててゲートウェイを再起動しました。

トラブルシューティング

awaiting gateway readinessで止まる

上述の通り、DISCORD_BOT_TOKENをopenclaw.jsonではなく、docker-compose.ymlのenvironmentに環境変数として設定してください。これで解決しなかったことは一度もありません。

エージェントが応答しない

ゲートウェイのログをリアルタイムで確認します。

$ docker compose logs -f openclaw-gateway

Discordでメッセージを送って、ログにメッセージ受信のイベントが出ているか確認してください。出ていない場合はペアリングが完了していないか、チャンネルのallowlist設定が間違っています。

blocked URL fetchのセキュリティログ

[security] blocked URL fetch target=http://localhost:18789/ reason=Blocked hostname

エージェントが内部URLにアクセスしようとしたときのSSRF対策です。エージェントが学習中に試みる典型的な動作で、無害なので無視してOKです。

エージェントが暴走してサーバー負荷が上がる

エージェントがツールを使ってDockerやローカルファイルにアクセスしようとして無限ループすることがあります。

# すぐ止めるにはゲートウェイを再起動
$ docker compose restart openclaw-gateway

原因はだいたいBOOTSTRAP.mdやワークスペースファイルに「エージェントにはできないタスク」が含まれていることです。OpenClawをOllamaと別ディレクトリで管理しておけば、docker compose downでOpenClawだけを止めてもOllamaは影響を受けません。

まとめ

OpenClaw + Ollama + Discordの組み合わせで、APIキー不要・トークンコスト完全ゼロのローカルAIエージェントが構築できました。

一番の学びは2つ。DISCORD_BOT_TOKENはopenclaw.jsonではなく環境変数で渡すこと。そしてqwen3.5:4bがエージェント用途では一番バランスが良いこと(9bはツール呼び出しのバグで不安定、8bは安定だが少し重い)。

Discordから「明日の予定教えて」とか「毎日AIニュースの情報を教えて」と話しかけるだけでローカルLLMが答えてくれる環境は、一度作ると手放せなくなります。