「IoTの基盤を自前で持ちたい。ただしデバイス管理もユーザー管理もダッシュボードも一から作りたくはない」——この要求に対して、OpenRemoteは4つのコンテナを立ち上げるだけで一通り揃うという答えを出している。2016年から続くJava製のプロジェクトで、2026年8月13日にもコミットが入っている現役のOSSだ。

OpenRemoteのアーキテクチャ全体図。デバイス層からエージェント、Manager、UIまでの構成
OpenRemote公式のアーキテクチャ図。デバイス/プロトコルエージェント/Manager/アプリ層の4段構成(出典: openremote/openremote README
この記事のポイント
  • ・4コンテナ(proxy/PostgreSQL/Keycloak/Manager)で、デバイス管理からダッシュボードまで揃う
  • ・既定composeは80・443・8883を全インターフェースに開く。メトリクスの8404/8405だけが127.0.0.1に縛られている
  • ・匿名MQTTは接続できるが、通れるのはprovisioning/{id}/requestの1本道だけ。X.509証明書のCNとトピックIDの一致が必須

この記事では、OpenRemoteが何を提供するのかを整理したうえで、導入判断に直結する3点——既定構成が外に開くもの、匿名MQTTの到達範囲、AGPL-3.0の意味——を一次ソースで確認する。ノーコードからコードまでの自動化ツール選定の全体像はAI自動化ツール|ノーコードからコードまで2026年版の比較と選び方にまとめてあるので、そちらも参照してほしい。

検証方法と日付(2026-08-15):AGENTS.mdの増加量・リポジトリ統計・比較表の数値は GitHub REST API(Contents API / Repos API)で同日に取得した実測値。ポート束縛はmasterブランチの docker-compose.yml、MQTTの認証境界は manager/src/main/java/org/openremote/manager/mqtt/ 配下のソース読解にもとづく。スタックを起動しての動作確認は行っていないため、実行時の挙動を断定する書き方は避け、根拠のファイル名を各節に明記した。

1. OpenRemoteとは——100% OSSのIoT基盤で何ができるか

OpenRemoteは、オランダのOpenRemote Inc.が開発するIoTプラットフォームである。「デバイスをつないで、貯めて、見せて、自動で動かす」までを1スタックで賄うことを狙っている。

OpenRemoteの実測スペック。AGPL-3.0、スター1842、最新リリース1.29.0、2016年開始
GitHub APIで取得した2026年8月15日時点の実測値

主要な構成要素は次のとおり。

Manager — 中核のJavaアプリケーション。アセット(デバイスや設備の抽象表現)を保持し、REST APIとMQTTブローカーを提供する
プロトコルエージェント — 外部機器との接続層。18種類が同梱されている
ルールエンジン — when-then/flow/JavaScript/Groovyの4層。ノーコードからコードまで段階的に書ける
Keycloak — 認証基盤。realm(レルム)単位でテナントを分離する
Insightsダッシュボード — 収集した時系列データを可視化するビルダー

対応プロトコルは、リポジトリの agent/src/main/java/org/openremote/agent/protocol/ 配下を数えると18種類ある(共通I/O基盤の io パッケージは除いた数え方)。産業系のModbus・KNX・SNMP、無線のZ-Wave・LoRaWAN・BLE、照明のArt-Net・TRÅDFRI、汎用のHTTP・WebSocket・TCP・UDP・シリアルまで揃う。

ここで正直に書いておくと、このプラットフォーム本体にLLM・MCP・OpenAI/Anthropic連携のコードは存在しない。リポジトリ説明文の「add intelligence」が指すのは上記のルールエンジンであり、AIのことではない。AIと接点があるのは第5章で扱うAGENTS.mdの1点だけで、そこは実測値のある面白い話になる。

アセットモデルという考え方

OpenRemoteの設計上の中心は「アセット」である。センサーやビル、EV充電器といった対象を型付きのアセットとして定義し、その属性(attribute)に値が流れ込む。属性ごとに履歴保存の可否や保持期間を設定でき、ルールエンジンやダッシュボードはすべてこのアセット/属性を参照する。

自動化は「3つのスコープ × 4つの書き方」で決まる

「結局どこまで自動化できるのか」は最も知りたい点だろう。OpenRemoteのルールは、適用範囲記述方法の2軸で整理すると分かりやすい。

適用範囲は model/src/main/java/org/openremote/model/rules/ にクラスとして定義されており、3種類ある。

GlobalRuleset — システム全体に適用。全realmを横断する
RealmRuleset — 特定のrealm(テナント)内だけに適用
AssetRuleset — 特定のアセットとその配下にだけ適用

記述方法は4層で、下に行くほど自由度が上がる。

書き方 対象者 用途の目安
when-then 非エンジニア 「温度が30度を超えたら通知」レベルの単純条件
flow 非エンジニア〜 値の変換・結線をGUIで組む
JavaScript エンジニア 条件が複雑で手続き的に書きたい場合
Groovy エンジニア JVM資産を呼びたい・重い処理を書く場合

同じ「テナントごとの自動化」でも、非エンジニアの運用担当がwhen-thenでしきい値を調整し、エンジニアがGroovyで複雑な計算を書く、という分担が同一基盤の上で成立する。ノーコードとコードが別ツールに分かれないのはこの構成の利点である。

なお実装側には RulesLoopException という例外クラスが用意されており、ルールが互いを発火させ続ける無限ループを上限回数で打ち切る仕組みが入っている。ルールを書き込む前提の基盤としては妥当な備えだ。

データはPostgreSQLに永続化される。READMEによれば履歴データは OR_DATA_POINTS_MAX_AGE_DAYS の値にもとづき日次で purge され、属性単位では dataPointsMaxAgeDays で個別に上書きできる。つまり放っておくと古い時系列データは消える設計なので、長期保存が要件なら値の見直しか外部への吐き出しが要る。PostgreSQLごとバックアップを取る運用にするなら、Portabaseのように複数DBをWeb UIからバックアップするOSSを併用する構成が素直だ。

2. OpenRemoteをDockerで動かす——起動手順と最初に確認すること

公式が案内する最短経路はDocker Composeである。amd64・arm64の両方のイメージが用意されている。

curl -O https://raw.githubusercontent.com/openremote/openremote/master/docker-compose.yml
docker compose pull
docker compose -p openremote up

起動すると https://localhost でManager UIに到達する。証明書は自己署名なので、ブラウザの警告を明示的に受け入れる必要がある。ログイン情報はREADMEに記載のとおり admin / secret である。

localhost以外のホスト名やIPでアクセスする場合は OR_HOSTNAME を与える。これはURL生成とKeycloakのホスト名検証に使われるため、省略すると認証リダイレクトが噛み合わない。

OR_HOSTNAME=192.168.1.10 OR_ADMIN_PASSWORD='<十分に長いランダム文字列>' \
  docker compose -p openremote up -d
最初のコマンドで済ませないこと
docker compose up だけで起動した状態は、管理者パスワードが secret のまま・証明書が自己署名のままである。README記載の既定値であって秘密ではないので、ネットワークに出す前に必ず上書きする。次章のポート束縛と併せて確認したい。

スタックは4サービスで構成される。docker-compose.yml を読むと依存関係が明示されており、PostgreSQL → Keycloak → Manager → proxy の順に healthy を待って立ち上がる。

graph LR subgraph "docker compose -p openremote" PG["postgresql
データ永続化"] --> KC["keycloak
認証・realm管理"] KC --> MG["manager
Java中核・REST・MQTT"] MG --> PX["proxy
HAProxy・TLS終端"] end DEV["デバイス"] -->|"MQTT 8883"| PX USR["ブラウザ"] -->|"HTTPS 443"| PX PX --> MG

起動後にまず確認したいのは「どのポートが、どこ向けに開いたか」である。ホスト側から次のように確認できる。

docker compose -p openremote ps --format 'table \t'
lsof -nP -iTCP -sTCP:LISTEN | grep -E '(:80|:443|:8883|:8404|:8405)\b'

3. 既定構成で何が外に開くか——ポート束縛のハードニング

ここからが導入判断で効く部分である。docker-compose.yml(masterブランチ)のポート定義を読むと、束縛先が2種類に明確に分かれていることがわかる。

既定composeのポート束縛。80・443・8883は全インターフェース、8404・8405はループバック限定
docker-compose.yml のports定義を分類した結果

全インターフェース(0.0.0.0)に開くのは3つ。

80:80 — Let’s Encryptによる証明書取得のために必要
${OR_SSL_PORT:-443}:443 — Manager UIとREST APIの入口
8883:8883 — MQTT over TLS。デバイスが接続してくる口

一方、メトリクスの2ポートは明示的にループバックへ縛られている。

127.0.0.1:8404:8404 — proxyのメトリクス
127.0.0.1:8405:8405 — managerのメトリクス

この非対称は読み取る価値がある。メトリクス側の行には「EC2 VMのプライベートサブネット側インターフェースのIPを想定」という趣旨のコメントが添えられており、束縛先を意識して書き分けていることがファイル上で確認できる。ここから、8883が全インターフェースで開くのは書き漏らしではなく意図的な設計と読める(デバイスが接続してくる口である以上、外に向いていなければ機能しない)。裏を返せば、LANに置いた瞬間からMQTTブローカーは同一セグメントの全ホストから到達可能になるので、そこに何が置かれているかを理解しておく必要がある(次章の主題)。

なお OR_DEV_MODE の既定値は ${OR_DEV_MODE:-false} であり、このcomposeは開発モードでは起動しない。ここは安全側に倒れている。

最低限のハードニング項目

ネットワークに出す前のチェックリスト
  • OR_ADMIN_PASSWORD を上書きする(既定は secret
  • OR_HOSTNAME を実際のホスト名にし、Let's Encryptで正規証明書を取得する(OR_EMAIL_ADMIN が必要)
  • ・8883をインターネットに晒す必要があるか検討する。デバイスがLAN内にしか居ないなら、composeを編集して 127.0.0.1: か特定IPに束縛するか、上位のファイアウォールで絞る
  • ・自動プロビジョニングを使うなら、X.509のCA証明書を自前で用意し、provisioning設定を有効化した状態で管理する

証明書の期限切れやコンテナ停止に気づけないと、デバイスからの書き込みが静かに止まる。TLS期限とHTTP応答の両方を見張るなら、Checkmateのような稼働監視OSSをDockerでセルフホストする構成が相性が良い。

4. 匿名MQTTはどこまで許されるか——自動プロビジョニングの認証境界

8883が外に開いている以上、「認証なしで何ができてしまうのか」は必ず確認すべき点である。結論から言うと、匿名接続は受け付けられるが、通れるのは自動プロビジョニング用の非常に狭い1本道だけだ。この節の内容は manager/src/main/java/org/openremote/manager/mqtt/ 配下のソース読解にもとづく。

匿名MQTTが通れる経路。接続、requestへのpublish、X.509提示、CNとトピックIDの一致
UserAssetProvisioningMQTTHandler.java の canPublish/canSubscribe から起こした経路

匿名ユーザーは実在する

ActiveMQORSecurityManager.java には ANONYMOUS_USERNAME = "anonymous" が定義されており、匿名の「ゲストユーザー」として認証が成立する経路がある。これはデバイスが初回接続時、まだ資格情報を持っていない状態でプロビジョニングを要求するために必要な仕組みだ。

通れるトピックは2つだけ

UserAssetProvisioningMQTTHandlercanPublishcanSubscribe は、それぞれ次の条件でのみ true を返す。

publishprovisioning/{uniqueId}/request にのみ許可
subscribeprovisioning/{uniqueId}/response にのみ許可
・いずれも、2番目のトークンがマルチレベル(#)・シングルレベル(+)のワイルドカードである場合は明示的に拒否される
・IDプロバイダがKeycloakでなければ、どちらも無条件で false

ワイルドカードを弾いている点が効いている。provisioning/+/response のような購読で他デバイスの応答をまとめて拾う、という手が塞がれている。

二重購読は「盗聴の可能性」として拒否される

さらに canSubscribe には、同じ応答トピックに対して既に生存している購読があれば拒否するという処理が入っている。ソース中のログ文言はそのものずばり「Subscription already exists possible eavesdropping」である。他人のプロビジョニング応答に相乗りする経路を潰す意図が読み取れる。

実際の関門はX.509証明書

匿名でrequestトピックにpublishできたとしても、その先に本命の検証がある。processX509ProvisioningMessage は次を順に確認する。

・証明書がパースできること
設定済みのプロビジョニング構成のCAに連なる証明書であること(一致する構成が無ければ拒否)
・その構成が無効化されていないこと
・証明書のSubject CNに一意IDが含まれること
CNの一意IDとトピックの一意IDが一致すること(不一致なら拒否)

つまり、匿名で入れる範囲は「自分が名乗ったIDと同じIDの証明書を、あなたが設定したCAで署名してもらっていること」を証明する場に限られる。CAを設定していなければ、そもそも一致する構成が無いので何も起こらない。

設計上のトレードオフ:認証キャッシュが無効化されている
MQTTBrokerService.javasetAuthenticationCacheSize(0)setAuthorizationCacheSize(0) を明示的に設定している。ソース中のコメントによれば、自動プロビジョニングのクライアントが匿名として認証され、その情報がServerSessionに焼き込まれて後から変更できないため、キャッシュを使うと全匿名セッションが同じキーを共有してしまうからだ。
結果としてMQTTの認可判定は毎回キャッシュを経ずに評価される。同コード上部にはX.509での認証に移行したいというTODOも残っている。大量デバイスを収容する計画があるなら、この点は負荷試験で確認しておきたい。

5. AGENTS.mdは実運用でどう育ったか——997バイトから16,248バイトへの37日間

前述のとおりOpenRemote本体にAI機能は無い。だがリポジトリ自体は、AIコーディングエージェントに向けた指示ファイル AGENTS.md を2026年に導入している。そしてこれが、AGENTS.mdの「実運用でどう育つか」を観測できる稀な事例になっている。

初版はPR #2848(作者 ebariaux、2026年7月1日マージ)で、変更行数はわずか16行の追加。PR本文には「just a start, work in progress(まだ始まりで、作業中)」とある。

そこから37日後の姿を、GitHub Contents APIで各コミット時点のサイズを引いて実測した。

AGENTS.mdのサイズ推移。997バイトから16248バイトへ37日で16.3倍
AGENTS.mdを変更した8コミットについて、各時点のバイト数をGitHub Contents APIで取得
日付 サイズ(バイト) 初版比 主な契機
2026-07-01 997 1.0倍 初版(16行・work in progress)
2026-07-13 9,164 9.2倍 UIコンポーネント規約の投入
2026-07-22 11,439 11.5倍 シミュレータ関連の変更に伴う追記
2026-07-24 14,229 14.3倍 通知機能の実装と同時
2026-07-27 14,396 14.4倍 座標入力の修正と同時
2026-07-29 15,704 15.8倍
2026-07-31 15,705 15.8倍 Spotless適用(+1バイト)
2026-08-07 16,248 16.3倍 アラーム表示の修正と同時

注目すべきは増えたタイミングである。8コミットのうち独立した「AGENTS.mdを書く」コミットは初版だけで、残りはすべて機能実装のコミットに相乗りしている。通知機能を実装した回、Vaadinトグルを実装した回、アラームのフィルタを直した回——実装で得た知見がその場でAGENTS.mdに書き足されている

何が書かれているか

中身も一般論ではない。実際に含まれているのは次のような、踏まないと分からない類の知識である。

・Flywayのスクリプト名は V20260611_0755__Changes.sql 形式(V は大文字必須)
./gradlew clean はPostgreSQLにマウントされる tmp/ を消すので、テスト前に作り直す必要がある
・アクセス制御はサービス層ではなくリソース実装に置く。エンティティを先に解決して404、次にrealm権限で403、という順序まで指定
・Vaadinラッパーで static get styles() を使ってはいけない理由(ThemableMixinがLumoより先に注入するため上書きされる)と、::part() かLumoの @media 注入を使う判断基準

4点目などは、実際にスタイルが効かずに何時間か溶かした人でなければ書けない粒度だ。AIコーディングエージェントに上級エンジニアの判断を渡すという発想はAddy OsmaniのAgent Skills|AIコーディングエージェントに上級エンジニアの思考を注入する24のスキルでも整理されているが、OpenRemoteのAGENTS.mdはそれを既存の大規模コードベースで自然発生的にやっている例と言える。

上限までの距離

ここで無視できない数字がある。OpenAI Codexがプロジェクト指示ファイルを読み込む際の既定上限は、codex-rs の設定に定義された project_doc_max_bytes = 32,768バイトである。

現在の16,248バイトは、この上限の49.6%にあたる。37日で16.3倍という増え方がこのまま続く保証はないが、機能実装のたびに数百〜数千バイトずつ積まれる現在のペースを見ると、上限は「いつか考えること」ではなく「そう遠くない話」になりつつある。

AGENTS.mdを運用する側への示唆
この事例が示すのは、AGENTS.mdは書いて終わりのファイルではなく、コードと同じ速度で育つ資産だということ。そして育つ以上、上限とトークン消費という制約に必ず当たる。分割(サブディレクトリへのAGENTS.md配置)を、上限に当たってからではなく設計時点で考えておくのが妥当だろう。AGENTS.md自体の書き方・3層ロード・上限設定については別記事で扱っている。

6. OpenRemoteとThingsBoard・Home Assistant・Node-REDの違い

「IoTで自前基盤」を検討すると必ず並ぶ選択肢と比較する。数値はいずれもGitHub APIで2026年8月15日に取得した実測値である。

  OpenRemote ThingsBoard Home Assistant Node-RED
スター 1,842 22,246 89,920 23,530
フォーク 455 6,404 38,349 3,879
主言語 Java Java Python JavaScript
ライセンス AGPL-3.0 Apache-2.0 Apache-2.0 Apache-2.0
初コミット 2016-02 2016-12 2013-09 2013-09
主用途 産業・都市・エネルギー基盤 汎用IoT基盤 家庭向けスマートホーム フローベース結線
マルチテナンシー Keycloak realm標準搭載 テナント概念あり 想定外 想定外

読み取れる分かれ目は3つ。

1. ライセンスが最大の差。OpenRemoteだけがAGPL-3.0で、他3つはApache-2.0である。これは規模やスター数より実務上の影響が大きい(次章)。

2. 規模の差は正直に見るべき。ThingsBoardの★22,246に対しOpenRemoteは★1,842で、約12分の1である。日本語情報に至ってはQiita・Zennに解説記事が1本も無い(本記事執筆時点で両サイトのAPI検索結果が0件)。情報を英語の公式ドキュメントとフォーラムから取る前提が必要になる。

3. 想定する対象が違う。Home Assistantは家庭、Node-REDは結線そのものが主役で、どちらも「テナントを分けて複数組織に提供する」用途は想定していない。OpenRemoteはKeycloakのrealmでマルチテナンシーを最初から持ち、X.509での自動プロビジョニングも組み込み済みで、この点は明確な設計上の選択である。

7. AGPL-3.0——自社プロダクトに組み込む前に確認すること

最後に、導入可否を左右する現実的な論点を整理する。

GitHubのAPIはOpenRemoteのライセンスを NOASSERTION と返す。これを「ライセンス不明」と読むのは誤りで、LICENSE.txt の冒頭には次のとおり明記されている——本プログラムはフリーソフトウェアであり、GNU Affero General Public License version 3(またはそれ以降)の条項の下で再配布・改変できる、と。自動判定が効かないのは、同ファイルが後半でANTLR・Logbackなど同梱third-partyライブラリのライセンス一覧を併記しているためである。

AGPL-3.0がApache-2.0と決定的に違うのは、ネットワーク越しの利用にも開示義務が及ぶ点である。改変したOpenRemoteをSaaSとして顧客に提供する場合、バイナリを配布していなくても、改変部分のソースを利用者が入手できる状態にする義務が生じうる。社内利用にとどまるのか、外部に機能提供するのかで話がまったく変わる。

ライセンスの事実であって、法的助言ではない
ここに書いたのは LICENSE.txt の記載とAGPL-3.0の一般的な性質であり、個別案件の判断ではない。自社プロダクトへ組み込む場合は必ず社内法務に確認してほしい。なおOpenRemote Inc.は商用ライセンスの提供元でもあるため、AGPLの条件が合わない場合の選択肢として直接問い合わせる道もある。

どんなときに選ぶか

ここまでの材料をまとめると、OpenRemoteが向くのは次のような場合だ。

テナント分離が要件——realm単位のマルチテナンシーが最初から入っている
デバイスをX.509で自動登録したい——プロビジョニング機構が組み込み済みで、証明書CNとトピックIDの一致まで検証される
産業プロトコルが必要——Modbus・KNX・SNMP・LoRaWANなどが同梱の18種に含まれる
AGPL-3.0で問題ない——社内利用、または改変部分の公開を受け入れられる

逆に、自社SaaSへ改変して組み込みたくソース公開は避けたい場合や、日本語での情報量を重視する場合は、Apache-2.0のThingsBoardを含めて比較する方が現実的だろう。

参照ソース

openremote/openremote — GitHub(README、docker-compose.yml、LICENSE.txt、manager/src/main/java/org/openremote/manager/mqtt/ 配下のソース、AGENTS.md、PR #2848。2026年8月15日にAPI経由で取得)
OpenRemote Documentation(Manager UIガイド、開発者ガイド、DBバックアップ手順)
OpenRemote 公式サイト(アーキテクチャ図、製品情報)
openai/codex — GitHubproject_doc_max_bytes の既定値 32,768 バイトの定義)