Docker
Docker で ObjectOS を実行します。イメージを固定し、必須の 3 つの変数を設定し、Compose で PostgreSQL を追加し、プローブとプロキシを接続し、アップグレードとロールバックを行います。Enterprise イメージでは、プライベートレジストリ、検証、ライセンスが加わります。
Docker は、自社のサーバーで ObjectOS を実行する標準的な方法です。手順はセルフマネージドの両エディションで同じです。正確に固定したひとつのイメージ、実行時に注入する設定、コンテナの外にあるデータベース。このページでは、オープンな Community イメージ ghcr.io/objectstack-ai/objectstack を使って手順を説明します。ライセンスされたイメージで変わる点は、最後の Enterprise で追加されるもののセクションで扱います。
イメージを固定する
Community イメージは ObjectStack のリリースごとに公開されます。タグは内部の ObjectStack CLI のバージョンを反映しています。リリース X.Y.Z は X.Y.Z としてプッシュされ、latest、X.Y、X がそこへ移動します。移動するタグは公開のたびに動くため、latest に対する日常的な docker compose pull によって、デプロイの操作なしに実行中のバージョンが置き換わることがあります。
正確なタグが固定するのはバージョンであって、バイトではありません。メンテナーは、たとえば node:22-slim ベースイメージのセキュリティパッチを取り込むために、すでにリリースされたバージョンを再ビルドすることがあり、その再ビルドは同じ X.Y.Z タグを新しいイメージに再びプッシュします。内部の ObjectStack CLI は同じバージョンのままですが、ベースイメージと、バージョン範囲でインストールされるデータベースドライバーは新たに解決されます。公開ワークフローがその再ビルドを宣言しています。同じバイトを永続的に指すのはダイジェストだけです。
本番では正確なバージョンを実行してください。 アプリをビルドした CLI と同じバージョンです。そして、すべてのホストで、すべての再起動後に同じバイトが必要な場所では、そのダイジェストを固定します。ダイジェストはレジストリから読み取ります。
docker buildx imagetools inspect ghcr.io/objectstack-ai/objectstack:X.Y.ZName: の下にある最初の Digest: の行を固定します。これはイメージ全体(両方のアーキテクチャ)を指します。Manifests: の下に並ぶダイジェストは、それぞれ 1 つのプラットフォームを指します。そのダイジェストでイメージを参照し、ダイジェストからはどのリリースかわからないので、バージョンを横に書いておきます。
# ObjectStack X.Y.Z
ghcr.io/objectstack-ai/objectstack@sha256:<64 hex characters>以前のバージョンとそのダイジェストは書き残しておいてください。それがロールバックです。イメージは linux/amd64 と linux/arm64 向けに公開され、非 root ユーザーで実行され、ポート 8080 で待ち受け、独自のヘルスチェックを持っています。PostgreSQL(pg)と MySQL(mysql2)のドライバーが含まれています。
設定する 3 つの値
それ以外はすべて、使えるデフォルトがあります。次の 3 つにはありません。
| 変数 | ない場合に起きること |
|---|---|
OS_DATABASE_URL | データはランタイムのホームディレクトリの下の SQLite ファイルに書き込まれます。永続的な単一ホストでは問題ありませんが、コンテナのファイルシステムとともに失われます。本番では postgres://… を使います。 |
OS_AUTH_SECRET | /api/v1/auth/* の下のサインインエンドポイントがスキップされ、サーバーは認証なしで実行されます。 |
OS_SECRET_KEY | ランタイムは保存されたシークレット用に独自のキーを生成し、自身のファイルシステムに保持します。コンテナではそのキーはコンテナとともに消え、それで暗号化されたすべてのシークレットが読めなくなります。 |
2 つのシークレットは一度だけ生成し、シークレットマネージャーに保管します。
OS_AUTH_SECRET=$(openssl rand -hex 32)
OS_SECRET_KEY=$(openssl rand -hex 32)どちらのシークレットもイメージに焼き込まないでください。 オーケストレーターのシークレットストアから実行時に渡します。OS_SECRET_KEY はデータベースと一緒にバックアップします。キーなしで復元したデータベースは、自身のシークレットを復号できません。バックアップを参照してください。完全な一覧は環境変数にあります。
実行する
イメージにはアプリが含まれていません。os build がコンパイルする objectstack.json を、次の 3 つの方法のいずれかで渡します。
アーティファクトをマウントする。 イメージのビルドは一切不要です。イメージは /srv/app/objectstack.json でアプリを探します。
docker run -p 8080:8080 \
-v "$PWD/dist/objectstack.json:/srv/app/objectstack.json:ro" \
-e OS_DATABASE_URL="postgres://objectos:secret@db-host:5432/objectos" \
-e OS_AUTH_SECRET \
-e OS_SECRET_KEY \
ghcr.io/objectstack-ai/objectstack:X.Y.Zアーティファクトを URL で指定する。 OS_ARTIFACT_URL は公開済みのアーティファクトを指します。固定したイメージと 1 つの変数で動くアプリになり、アプリのアップグレードはその変数の変更と再起動で済みます。
docker run -p 8080:8080 \
-e OS_ARTIFACT_URL="https://releases.example.com/crm-2.2.2.json#sha256=<64 hex characters>" \
-e OS_DATABASE_URL -e OS_AUTH_SECRET -e OS_SECRET_KEY \
ghcr.io/objectstack-ai/objectstack:X.Y.Z任意の #sha256= フラグメントはアーティファクトのバイトを固定し、ランタイムは起動時にそれを守ります。
| 状況 | ランタイムの動作 |
|---|---|
| 固定なし | 検証なしで起動します。アーティファクトを取得できない場合、オーケストレーターが再試行するよう起動は失敗します。フォールバックできるキャッシュはありません。 |
| 固定あり、バイトが一致 | 起動し、検証済みのコピーを保持します。 |
| 固定あり、バイトが不一致 | 期待するダイジェストと実際のダイジェストを示して起動を拒否します。 |
| 固定あり、ホストに到達不能 | キャッシュされたコピーが固定と一致する場合に限りそこから起動し、警告でその旨を表示します。 |
マウントしたファイルには file:///… も使えます。アーティファクトは不変で、バージョン名の付いたオブジェクトとして公開し、書き込みアクセスはリリースパイプラインだけに与えてください。そうすることで、アプリのロールバックがロールフォワードと同じ操作になります。
アーティファクトを自分のイメージにコピーする。 リリースごとに自己完結したイメージを 1 つ作ります。プロジェクトの雛形(npm create objectstack)が、この形の Dockerfile を書き出してくれます。
FROM node:22-slim AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* package-lock.json* ./
RUN if [ -f pnpm-lock.yaml ]; then corepack pnpm@10 install --frozen-lockfile; \
else npm ci; fi
COPY . .
RUN npx os build # → dist/objectstack.json
FROM ghcr.io/objectstack-ai/objectstack:X.Y.Z
COPY --from=build --chown=node:node /app/dist/objectstack.json /srv/app/objectstack.jsonPostgreSQL と Compose で構成する
単一ホストの本番スタックです。上記のイメージと、その横のデータベース。
services:
app:
build: .
ports:
- "8080:8080"
environment:
OS_DATABASE_URL: postgres://objectos:${POSTGRES_PASSWORD}@db:5432/objectos
OS_AUTH_SECRET: ${OS_AUTH_SECRET}
OS_SECRET_KEY: ${OS_SECRET_KEY}
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:17
environment:
POSTGRES_USER: objectos
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: objectos
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U objectos -d objectos"]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped
volumes:
pgdata:# .env beside docker-compose.yml — never committed
POSTGRES_PASSWORD=…
OS_AUTH_SECRET=…
OS_SECRET_KEY=…
docker compose up -d
curl -fsS http://localhost:8080/api/v1/healthマネージド PostgreSQL に移行するときは、OS_DATABASE_URL をそこに向けて db サービスを削除します。スキーマはランタイムの起動時に最新の状態にされます。安全な変更は自動的に実行され、破壊的な変更は、os migrate apply --allow-destructive で意図的に適用するまで、各変更を示して起動を拒否します(CLI)。
ヘルスチェック
エンドポイントは 2 つあり、互換性はありません。
| エンドポイント | 答える内容 | 接続先 |
|---|---|---|
/api/v1/health | プロセスが起動し、HTTP を提供している。データベースには一切触れません。 | Liveness |
/api/v1/ready | ランタイムが起動し、データドライバーが応答している。応答しないドライバーがある間は、そのドライバーを示して 503 を返します。 | Readiness、およびロードバランサー |
取り違えると、ひどい失敗になります。/api/v1/ready に対する liveness プローブは、データベースの障害時にすべてのレプリカを再起動させ(再起動では直りません)、設定の不具合を、それを隠す再起動ループに変えてしまいます。反応すべきなのは readiness です。データベースが不在の間はレプリカをローテーションから外し、データベースが戻れば再起動なしで元に戻します。アプリがバインドするデータソースも起動時に接続できなければならず、そうでなければ起動はその場で失敗します。到達できないデータベースはデプロイの失敗であり、起動してからすべてのリクエストでエラーを返すコンテナではありません。
リバースプロキシと TLS
ランタイムの前段で TLS を終端し(Caddy、nginx、Traefik、またはクラウドのロードバランサー)、3 つの値を公開アドレスと揃えておきます。
OS_AUTH_URL=https://app.example.com # sign-in callbacks and cookie origin
OS_TRUSTED_ORIGINS=https://app.example.com # sign-in callback and CSRF allow-list
OS_PORT=8080 # the proxy's upstream portapp.example.com {
reverse_proxy localhost:8080
}ポートやオリジンのずれは、セルフホスティングの典型的な失敗です。アプリは動くのに、サインインは跳ね返され、ブラウザは API 呼び出しをブロックします。Console と API が同じ公開オリジンを共有している間は、CORS の設定は不要です。別のオリジンから提供されるフロントエンドは、OS_CORS_ORIGIN に明示的に列挙します。認証情報が有効な間、* は起動時に拒否されます。HSTS は TLS を確認してから有効にしてください。残りは本番環境への準備にあります。
初回起動:最初の管理者
新しいデータベースでは、デプロイメントのアドレスを開いて登録します。最初のアカウントが管理者になります。 最初のデプロイの直後、アドレスを共有する前に行ってください。本番サーバーはデフォルトの認証情報を投入しません。os dev で見覚えのある admin@objectos.ai アカウントは、開発用データベースにしか存在しません。
代わりに設定で管理者を指定するには、OS_PLATFORM_OWNER_EMAIL に 1 つのアドレスまたはカンマ区切りのリストを設定します。列挙された各アカウントは、メールアドレスが確認されるとその地位を持ちます。マルチ組織の姿勢ではこれが必須です。環境変数を参照してください。
サインインアカウントなしで人のレコードを投入するアプリは、この入り口を閉ざします。最初のアカウントのルールは、ログインではなく人を数えるからです。起動ログにその旨が表示されます。トラブルシューティングを参照してください。
マルチノード
ObjectOS Community はレプリカ 1 つで実行します。ロック、キュー、スケジュールといった調整はプロセス内にあるため、2 つ目のレプリカはそれを共有しません。ライセンスなしで複数のレプリカを宣言すると(OS_CLUSTER_REPLICAS が 1 より大きい)、プロセスごとのロックで実行するのではなく、起動を拒否します。マルチノードでの運用は Enterprise ライセンスの機能で、現在は Enterprise キットも 1 つのノードで動作します。後述のマルチノードを参照してください。
アップグレードとロールバック
- 通過するすべての ObjectStack バージョンのリリースノートと、それらがオペレーターに求めるバージョン固有の手順を読みます。
- データベースをバックアップします。 スキーマのマイグレーションは前にしか進みません。イメージをロールバックしても、スキーマはロールバックされません。
- イメージを新しい正確なバージョン(ダイジェストを固定している場合はそのダイジェスト)に変更し、再起動します。スキーマは、ランタイムが準備完了を報告する前の起動時に最新の状態にされます。
/api/v1/readyが 200 を返すことを確認します。- ロールバックするには、以前のタグまたはダイジェストに戻して手順 3 を繰り返します。離れるバージョンが互換性のないスキーマ変更を行っていない限り有効です。
アプリは別にバージョン管理されます。OS_ARTIFACT_URL やマウントするアーティファクトを変更すれば、イメージに触れずにアプリをアップグレードできます。
Enterprise で追加されるもの
ライセンスされた Enterprise イメージも同じ方法で実行し、プライベートレジストリ、サプライチェーンの検証、ライセンス、マルチノードが加わります。固定すべきダイジェストはリリースノートに記載されています。リリースにはデプロイバンドルが付属し、Compose スタック、デプロイメント形態ごとの環境テンプレート、そして正確なバージョンのコマンドを記載した README が含まれます。このセクションは、そのバンドルが設定するものの判断マップです。
提供されるもの
ライセンスには、プライベートなランタイムイメージ用のレジストリの認証情報が付属します。サインインにはパッケージの read アクセスを持つトークンが必要です。公開の Enterprise イメージやソースからのビルドはありません。イメージは、配布されないソースから私たちのリリースパイプラインが生成するため、docker build という手段は存在しません。
サポートされるアーキテクチャ:linux/amd64 のみ。 これは記載漏れではなく宣言です。ビルドは、宣言していないアーキテクチャのイメージの生成を拒否します。Apple Silicon や Graviton のホストではエミュレーションで実行されることになり、試すには問題ありませんが、本番には不適切です。
ダイジェストでイメージを固定する
本番で実行するイメージの参照は、タグではなくダイジェストです。
OS_EE_IMAGE=<the registry reference from your release>@sha256:<64 hex characters>タグは人が読める名前であり、別のバイトに付け替えられることがあります。ダイジェストはコンテンツアドレスです。同じダイジェストは、どのホストでも永遠に同じイメージです。タグでは、問題が起きたときに実際に何が動いていたかを後から証明する方法がありません。ダイジェストはリリースノートに記載されているので、そのまま貼り付けてください。以前のダイジェストも残しておきます。
イメージは自分が何であるかを示せる
イメージには 2 つの異なるバージョン番号が付いており、混同してはいけません。ObjectOS の製品バージョンと、それに組み込まれた ObjectStack ランタイムの互換性範囲で、後者は独自のスケジュールで動きます。どちらも、イメージのビルド元となったソースのリビジョンとともに、OCI ラベルおよび機械可読なリリースマニフェストとしてイメージ自体の中に記録されています。どのビルドを持っているかを私たちに尋ねる必要はありません。
このマニフェストは、サポートリクエストに添付するものとして最も役立つものでもあります。それだけで正確なイメージを再現できます。
本番の前にイメージを検証する
イメージは(リリースパイプラインの OIDC アイデンティティによるキーレス方式で)署名されており、SBOM とビルドの来歴が付属します。本番へのロールアウトの前に一度検証してください。3 つのチェックは、それぞれ異なる問いに答えます。
| チェック | 答える問い |
|---|---|
| 署名 | このダイジェストは、他の誰かではなく ObjectOS のリリースパイプラインによって生成されたか |
| SBOM | このイメージの中に何が入っているか |
| 来歴 | このイメージはどのソースから、どのようにビルドされたか |
バンドルの README には、照合すべきアイデンティティと発行者を含む検証コマンドが、リリースに合わせて記載されています。
ライセンス
ライセンスは通常の手順の一部です。 ライセンスなしの Enterprise イメージの動作は、何を求めたかによって異なります。
- 単一組織、ライセンスなし:デプロイメントは ObjectOS Community として実行されます。オープンな MCP サーバーと、自前の AI の持ち込み。何も動かなくなることはありません。
- 単一組織、ライセンスあり:自社のインフラストラクチャ上で製品内 AI が有効になります。AI Builder と「データに質問する」です。
- マルチ組織(壁で区切られたテナンシーの姿勢)、ライセンスなし:ランタイムはライセンスを名指しして起動時に終了します。組織間の分離はライセンスされた機能であり、分離を求めたデプロイメントが、分離があるふりをしてトラフィックを提供することがあってはなりません。
同じ流れに 2 つ目の制約があり、こちらが人を驚かせるものです。ライセンスにはモードがあり、そのモードはクラウドの姿勢と結びついています。 通常のライセンスはコントロールプレーンに対してオンラインで検証され、エアギャップライセンスはローカルで検証されてネットワークを一切必要としません。通常のライセンスと「コントロールプレーンなし」の組み合わせは、劣化した状態ではなく不可能な状態です。後でライセンスについての誤解を招く苦情で失敗するのではなく、両方の設定を名指しして起動時に拒否されます。サポートされる組み合わせの完全な表はエアギャップにあります。
壁で区切られたデプロイメントはさらに 2 つの判断を宣言しなければならない
マルチ組織の姿勢では、2 つの設定に安全なデフォルトがないため、ランタイムは決めるまで起動を拒否します。どちらの値を宣言しても受け入れられますが、決めないことは受け入れられません。
| 判断 | デフォルトにできない理由 |
|---|---|
| 新しいサインアップが何に参加するか | メンバーシップそのものがテナントの境界である場所では、メンバーシップは明示的な行為(招待、管理者、SSO プロビジョニング)によってのみ付与されるべきです。自動参加は、既存の組織の中に見知らぬ人を入れてしまいます。 |
| どの AI エージェントをマウントするか | 「ask」エージェントは読み取ります。「build」エージェントはメタデータを作成します。そしてメタデータの範囲は組織ではなくデプロイメントです。共有データベースでは、ある顧客の build のターンが、他のすべての顧客が使うスキーマを書き換えます。 |
単一組織のデプロイメントでは、どちらのチェックも行われません。
提供されるスタック
バンドルの Compose スタックは、デプロイメントをあるべき順序で進めます。他のどのオーケストレーターでもこの順序を再現する必要があるため、知っておく価値があります。
- データベースが正常な状態になります。
- 1 回限りのマイグレーションがスキーマを完了まで実行します。アプリケーションのレプリカが起動する前なので、同時に動くレプリカが互いにスキーマ変更を実行し合うことはありません。
- アプリケーションのレプリカが起動します。イメージは独自のヘルスチェックを持っています。
- リバースプロキシは、レプリカが単に起動しただけでなく実際に応答するようになってから転送を始めます。
本番では、デプロイメントをマネージドの PostgreSQL(クラスター構成の場合は Redis も)に向け、バンドルのデータベースサービスを削除します。自動 HTTPS を得るには、プロキシにドメインを指定します。ランタイムのコンテナは非 root ユーザーで実行され、ランタイムの状態を宣言されたボリュームに保持します。
Enterprise でのマルチノード
現在、Enterprise キットは 1 つのノードで実行してください。 デプロイバンドルにはクラスタードライバーが組み込まれていないため、OS_CLUSTER_REPLICAS は 1 のままにし、docker compose up --scale でアプリのサービスをスケールしないでください。
OS_CLUSTER_REPLICAS を 1 より大きくすると、設計どおり起動が拒否されます。組み込みのプロセス内コーディネーターは、ロック、カウンター、pub/sub を各プロセスの中に保持するため、2 つ目のレプリカは 1 つ目と黙って食い違うことになります。OS_ALLOW_MEMORY_CLUSTER_MULTINODE でこの拒否を黙らせたり、--scale で回避したりしないでください。compose テンプレートは、レプリカ数とランタイムが読む OS_CLUSTER_REPLICAS を 1 つの変数に結び付けているので、両者が食い違うことはありません。
マルチノードのトポロジーは Enterprise ライセンスで有効になります(ノードの上限はライセンスの maxNodes)。契約で有効化され、契約が必要とする場合にはバンドルにクラスタードライバーが接続されます。ここで日付は約束しません。高可用性は、現在キットに含まれるものではありません。複数のノードが必要な場合は営業にお問い合わせください。
ノードの数にかかわらず、2 つのルールが当てはまります。
- 1 つの共有シークレットキーを設定します(
OS_SECRET_KEY)。それがないと、ランタイムは独自のキーを生成して永続化し、そのキーはレプリカ間で食い違います。 - 認証シークレットは、ランタイムが動作するすべての場所で同一でなければなりません。変更すると既存のすべてのセッションが無効になり、署名キーが宙に浮きます。ローテーションに伴う 1 回限りのクリーンアップは、環境テンプレートに記載されています。
ダイジェストでアップグレードする
上記のアップグレード手順は、タグの代わりにダイジェストを使って同じように適用されます。
- リリースノートから新しいダイジェストを取得し、ロールアウトの前にイメージのリリースマニフェストを読みます。製品のメジャーラインが現在のものと同じであることを確認し、組み込まれたランタイムの互換性範囲が動いたかどうかを確認します。ランタイムのメジャーバージョンの移動は下層に破壊的変更があることを意味するため、先にそのリリースのノートを読んでください。
- データベースをバックアップします。
- イメージのダイジェストを変更し、スタックを再起動します。マイグレーションは、新しいレプリカがトラフィックを受ける前に完了まで実行されます。
- readiness が 200 を返し、レプリカが正常と報告することを確認します。
- ロールバックするには、以前のダイジェストに戻して手順 3 を繰り返します。離れるバージョンが互換性のないスキーマ変更を行っていない限り有効です(行った場合はリリースノートに記載されています)。
次へ
- Kubernetes:オーケストレーター上の同じイメージ。
- 本番環境への準備:本番稼働前のチェックリスト。
- オブザーバビリティ:ログ、メトリクス、監査。
- エアギャップ:切断されたサイト向けの Enterprise ライセンスモード。