Docker Compose 生産展開
このページでは、Aivoryの生産 compose ドキュメントについて詳しく説明します。deploy/docker-compose.prod.yml: 各サービスの責任は、健康チェック、データボリュームとバックアップ戦略、プレビルド・グラフィックとローカル・ビルドの切り替え、リソースプラン、毎日の運用コマンド、アップグレードプロセス、そして一般的な故障のチェックリストです。サービスをできるだけ早く引っ張りたい場合は、まずチェックしてください。迅速な展開このページでは、最初のデプロイを完了したと仮定します。
構造概要
スタックは5つのサービスで構成されており、すべてプライベートブリッジネットワークに繋がっています。internal上です(ただ)appホストポートを公開する**(デフォルト)80:8787), Qdrant と Sandbox を含む他のサービスは、ポートを外部に露出しません、プライベート ネットワーク内でのみアクセスできます。
| サービス | 鏡像 | ホストポート | 義務 |
|---|---|---|---|
app | ghcr.io/<IMAGE_OWNER>/aivory-app | 80(変更可能) | 単一容器同源サーバー構築後のSPAと/api後端 |
postgres | postgres:16-alpine | 无 | 関係型ストレージ:ユーザー、会話、ナレッジベース、使用量など |
redis | redis:7-alpine | 无 | キャッシュ、フロー制限カウンター、プロセスストップストリーム出力の pub/sub |
qdrant | qdrant/qdrant:v1.12.4 | 无 | RAG ベータ検索 |
sandbox | ghcr.io/<IMAGE_OWNER>/aivory-sandbox-sidecar | 无 | 内蔵コード実行 サンドボックス コントロール面、内蔵ネットワークのみ |
appコンテナ内の Go プロセスは、SPA 静的ファイルと/api逆に、前後端は自然の同源なので、境界を越える問題もありません。必要ない配置PUBLIC_ORIGIN、ALLOWED_ORIGINSまたはドメイン名に関連する変数:エージェントはリクエストをコンテナに転送し、どのドメイン名が入ってどのドメイン名が使用されるか、複数のドメイン名を同時に指すことも問題ありません。リバースプロキシとHTTPS)。
サービス詳細
App:メインコンテナアプリケーション
- 容器内での監視
8787(AIVORY_LISTEN: ":8787"同時に、SPAと/api画像の作成時にSPAディレクトリが組み込まれている(STATIC_DIR鏡の内部のビルド製品を指します) 別々の nginx/web レイヤーは必要ありません。 - ポート マッピングは compose 文書で死にました:
"80:8787"ホスト 80 が占領されるとき、左側の数字を直接変更します(たとえば"8080:8787"環境変数は必要ありません。 - スタート依存:
postgres和redis健康診断(condition: service_healthy),qdrantスタートしたのは(condition: service_startedなぜなら、このサービスは healthcheck を定義していないからである。 app依存しないsandbox:サンドボックスはリクエストに応じてソフト依存であり、コード実行機能のみが使用されます。 スタート依存として作られると、サンドボックスの鏡が引き下げられず、または不健全な場合にアプリケーション全体を引き下げるので、 composeはこの依存を宣言しないことを意図します。- 健康診断(鏡)
Dockerfile.app中定義): 15sごとに使用wget探検http://127.0.0.1:8787/api/health超時間3s、スタート幅20s、失敗5連続で不健康判定。 - データカタログ:
${DATA_DIR:-./data}コンテナに縛り付ける/app/dataアップロードされたファイル、生成されたプロダクト、バックアップアーカイブ(および SQLite ファイル、使用する場合)を格納します. Docker ボリュームではなくホスト ディレクトリに貼り付けることは、これらのファイルをホストに直接表示してバックアップできるようにします。
コンポーネントapp注入された重要な環境変数(完全なリストを参照)主要な環境変数):
| 変数 | compose の値 | 説明 |
|---|---|---|
AIVORY_ENV | production | 配備レベルでのセキュリティ検査(例えば、JWT_SECRET強制) |
DATABASE_URL | postgres://<user>:<password>@postgres:5432/<db>?sslmode=disable | 由 .envPostgres 変数を組み合わせる |
REDIS_URL | redis://:<REDIS_PASSWORD>@redis:6379/0 | Redis キャッシュ、配列およびストリーム回復を有効にする |
QDRANT_URL | 仮認http://qdrant:6333 | スタック内の Qdrant を指し、外部クラスターとしてカバーできます。 |
QDRANT_API_KEY | 仮認aivory-internal-qdrant | 必須とqdrantサービスのキーが一致する(以下) |
JWT_SECRET | 必須在.env設定 | compose が欠落した場合、起動を拒否する |
SANDBOX_BASE_URL | http://sandbox:8000 | 密輸はネットアクセスを組み込みサンドボックス |
SANDBOX_API_KEY | 仮認aivory-bundled-sandbox | サンドボックス サービスで共有される内部デフォルト値 |
ENABLE_MOCK_PROVIDER | 仮認false | 置 true内蔵のデモモデルを有効にし、実際の API キーは必要ありません |
Anthropic/OpenAI などのプロバイダーの API キー環境変数による設定なしデータベースの channels テーブルに保存され、デプロイが完了した後、管理コンソールのチャンネルとモデルページを追加。
postgres: 関係型データベース
- 鏡像
postgres:16-alpineデータは名称に落ちます。pgdata(容器内)/var/lib/postgresql/data)。 POSTGRES_PASSWORD必須項目:compose 使用${POSTGRES_PASSWORD:?...}エラーを直接報告しない構文 ユーザー名とライブラリ名は既定です。aivory。- 健康診断:10秒ごとに実施
pg_isready5s を超え、最大 10 回試行します。app健康になってから始める。 - データベース Schema
app起動時に自動で作成および移行し、手動で SQL を実行する必要はありません。
POSTGRES_PASSWORD単に在pgdataボリュームは空で、データベースの初期化時に有効です。.envその中の価値ノーデータベース内の実際のパスワードを更新すると、app新しいパスワードで古いライブラリを接続すると認証が失敗します。パスワードを変更するか、データベース内で使用するかALTER USER実行、または(データを捨てられる環境のみ)削除pgdataボリューム再起動。
redis:キャッシュとニュース
- 鏡像
redis:7-alpineで、--appendonly yes --requirepass <REDIS_PASSWORD>起動: AOF 永続化を有効にし、パスワード認証を強制します。REDIS_PASSWORD同様に.env必需品です。 - キャッシュ、ストリーム制限カウンター、プロセス間の「停止生成」信号の pub/sub を担い、Redis を構成すると、アプリケーションは Redis コアとストリームの回復を有効にします。
- データは名称に落ちる
redisdata(容器内)/data)。 - 健康診断:10秒ごとに実施
redis-cli -a "$REDIS_PASSWORD" ping返品をチェックPONG5sを超え、10回繰り返します。
qdrant: ベクトルデータベース
- 鏡像
qdrant/qdrant:v1.12.4データは名称に落ちます。qdrantdata(容器内)/qdrant/storage)。 - Qdrant は、すべてのリクエストに対して API キーを要求します(
QDRANT__SERVICE__API_KEY(ステージ内)qdrant与app同じ読み方.env変数QDRANT_API_KEY両者共通の価値観aivory-internal-qdrantQdrant はホストポートをリリースしていないため、プライベート ネットワーク内でのみ利用可能であるため、この共有デフォルトは受け入れられます。.env内は強力なランダム値でカバーされます。 - ヘルスチェックの定義がありませんので、
appそれを待つだけ。service_started. Qdrant が一時的に利用できなくなると、機能が中断されることはありません: ベクトル回収が失敗した場合、RAG は全文を注入したコンテンツに戻ります。 - Collection は embedding 次元で名前を付けます。
aivory_c<维度>(たとえば、1536 次元モデル 対応)aivory_c1536)。
sandbox: コード実行 サンドボックス コントロール
サンドボックス セット 1個docker compose up単独のアイテムや追加の構成キーを必要としません。その完全なセキュリティ モデルと変換説明を参照コード サンドボックス 展開ここでは、 compose レベルでの行動について述べています。
- このサービスは sidecar コントロール面です: ホストを横断して
/var/run/docker.sockホスト Docker ガード プロセスで、セッションごとに 1 つの制限コンテナをリリースし、セッションの実行時刻を映像化ghcr.io/<IMAGE_OWNER>/aivory-sandbox開始時点で(SANDBOX_PULL_ON_START: "1")は、最初の呼び出しが使用可能であることを保証します。 - ホストポートを公開しない: たった
appプライベートネットワークを通じてhttp://sandbox:8000訪問!吊るすdocker.sockホストの root 権限に等しい「外部に露出しないポート」は、このリスクをコントロールするための鍵です。ports映射する。 - コントロール容器自体はリソースの上限を持っています:
mem_limit: 1g、pids_limit: 512セッションコンテナのリソースは、環境変数によって制御されます:
| 変数 | デフォルト値 | 意味 |
|---|---|---|
SANDBOX_NETWORK | none | セッションコンテナにはネットワークがありません; サンドボックス内コードがネットワークに接続する必要がある場合にのみbridge |
SANDBOX_MEMORY | 2g | 単一セッションコンテナのメモリ上限 |
SANDBOX_CPUS | 1 | 単一セッションコンテナ CPU クォータ |
SANDBOX_MAX_SESSIONS | 16 | 同期会話コンテナ数の上限 |
SANDBOX_EXEC_TIMEOUT_CAP_MS | 600000 | ハードタイムの上限 (10 分) を実行する場合、管理コンソールのオーバータイム設定はこの値の範囲内に制限されます。 |
SANDBOX_IDLE_TTL_CAP_SECONDS | 86400 | 無料のリサイクルウィンドウのハードトップ(24時間)、同様に制御 管理コンソール 設定 |
SANDBOX_WORKSPACE_SIZE | 512m | セッションワークゾーン容量 |
SANDBOX_READ_ONLY_ROOTFS | 1 | 会話コンテナ ルート ファイル システム 読み込みのみ |
- ワークスペースの持続性:セッションがリサイクルされた場合、
/workspaceパッケージ名に登録します。sandbox-archives(セッション ID キーを押します) 同じセッションがコードを再度実行すると自動的に回復します。このローカルアーカイブのゼロ設定は利用できますが、単機のみ複数のコピーのデプロイは、S3/OSSクラスのオブジェクトストレージに置き換えなければなりません。 - 健康チェック: Python で 30 秒ごとに
urllib検出容器内http://localhost:8000/healthz(鏡にはPython以外の探知ツールがない)、超時10s、再度3回、start_period为 120sこの許可期間は、冷たいスタート時にセッションの実行時刻のイメージを引っ張るのにかかる時間をカバーし、サービスが完了する前にリクエストを受け入れ始めることはありません。
データの永続化とバックアップ
データはどこに
| 巻 / 吊るし | コンテナ経路 | コンテンツ | 失われた結果 |
|---|---|---|---|
pgdata(名称巻) | /var/lib/postgresql/data | すべての関係データ:ユーザー、会話、ナレッジベース、設定 | 壊滅的でバックアップが必要 |
qdrantdata(名称巻) | /qdrant/storage | RAG ベータ | データベース内の chunk テキストから再構築できますが、再度埋め込まれた API 呼び出しを消費します。 |
redisdata(名称巻) | /data | キャッシュおよび制限流量カウンター(AOF) | 影響が小さく、再起動後に自然再生 |
sandbox-archives(名称巻) | /var/lib/aivory/sandbox-archives | 各 会話 の サンドボックス ワークスペース アーカイブ | 対応 会話 の サンドボックス ファイルが失われており、会話自体に影響を与えません |
DATA_DIR(宿泊者名簿は貼り付け、デフォルト./data) | /app/data | 書類の作成、書類作成、書類の作成(backups/子カタログ) | ユーザーがアップロードし、製品が失われ、バックアップする必要があります。 |
-v名前と共に削除されます。pgdata、qdrantdataすべてのデータが現地で空っぽで回復できないようにします。毎日サービス停止docker compose -f docker-compose.prod.yml down(持ってない)-v)またはstop。
バックアップ戦略
並行する2つのルート:
- アプリケーションレベルのバックアップ(優先): コンソールの管理 Backup & Migration ページは、エンジン中立のデータベースロジックバックアップ(テーブルごとに 1 JSONL)、オプションの uploads/artifacts ファイル、オプションの Qdrant ベクトルデータを含む完全な移行 ZIP を同期して生成します。
BACKUP_DIR(容器内表示)/app/data/backupsホスト機の対応DATA_DIR/backupsこのバックアップは、エンジン間で復元できます(例えば、SQLite デプロイに復元できます)。バックアップと移行。 - インフラレベル冷却すべてのタブレットを閉じた後、すべてのタブレットと
DATA_DIRカテゴリと共に写真/コピー。**一緒に備えなければなりません。**データベースの行、ベータ、およびディスクファイルの3つが一致することを保証します。
入力するサイズの上限はMAX_BACKUP_BYTES制御は、デフォルトで 20 GiB; ベクトルを含む全量のアーカイブは大きい可能性があり、必要に応じてアップグレードされます。
現地構築と鏡の構築
app 和 sandbox2つのサービスが同時に発表された。image: 和 build:コンポーシングの行動は、鏡が存在する時(または引っ張られる時)に鏡が優先され、--buildパラメータはローカルに構築され、同じ compose ファイルは 2 つのプロセスで使用され、トポーは 2 回書く必要はありません。
方法1:プレビュー鏡を引っ張る(生産に推奨)
cd deploy
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
方法2:ソースコードからローカルに構築(二次開発または公式の鏡像で覆われていないアーキテクチャ用)
cd deploy
docker compose -f docker-compose.prod.yml up -d --build
鏡の由来.env2つの変数制御:
| 変数 | デフォルト値 | 説明 |
|---|---|---|
IMAGE_REGISTRY | ghcr.io | 镜像仓库地址;GitHub 访问受限时可整体切换到 ghcr 镜像代理或自建/私有仓库,见下方「受限网络部署」 |
IMAGE_OWNER | hjxwz123 | ghcr.io 名前空間;fork は自分のアカウントに自分のユーザー名に変更します。 |
IMAGE_TAG | latest | 鏡のラベル;生産環境は特定のバージョンラベルに固定することを推奨し、アップグレードとバックロールの両方がより制御可能 |
地元の建設において、app下記の構造は倉庫根のカタログです。Dockerfile.appNode 20 で Vite SPA を構築(構築機のネイティブアーキテクチャで実行され、プロダクトはアーキテクチャに関係のない静的ファイル)し、Go 1.24 でコンパイル API バイナリ (CGO が必要であるため、バイナリは SQLite ドライブを開発/リバックバックエンドとして組み込んでいます)を組み込む。debian:bookworm-slim動作時の鏡。
受限网络部署(无法访问 GitHub)
服务器连不上 GitHub / ghcr.io 时,部署不受阻——按下面三步走。
第一步:拿到部署文件(不需要 git clone)
部署只需要两个文件:compose 文件和 .env。compose 文件可直接从本文档站下载(文档站部署在 Cloudflare,不依赖 GitHub):
mkdir -p aivory/deploy && cd aivory/deploy
curl -LO https://aivory-docs.pages.dev/deploy/docker-compose.prod.yml
# 然后在同目录创建 .env(至少包含 POSTGRES_PASSWORD、REDIS_PASSWORD、JWT_SECRET)
第二步:解决 ghcr.io 镜像拉取
三个应用镜像(aivory-app、aivory-sandbox-sidecar、会话运行时 aivory-sandbox)默认来自 ghcr.io。任选其一:
方式 A:切换镜像代理(最省事)——.env 里加一行,三个镜像整体换源:
# 任何兼容 ghcr 的代理/镜像站或你的私有仓库地址
IMAGE_REGISTRY=ghcr.nju.edu.cn
方式 B:离线搬运(完全不通外网的内网)——在任意能访问 ghcr.io 的机器上导出,再传到服务器导入:
# 有网机器
docker pull ghcr.io/hjxwz123/aivory-app:latest
docker pull ghcr.io/hjxwz123/aivory-sandbox-sidecar:latest
docker pull ghcr.io/hjxwz123/aivory-sandbox:latest
docker save ghcr.io/hjxwz123/aivory-app:latest ghcr.io/hjxwz123/aivory-sandbox-sidecar:latest ghcr.io/hjxwz123/aivory-sandbox:latest | gzip > aivory-images.tar.gz
# 目标服务器
docker load < aivory-images.tar.gz
离线导入后 サンドボックス sidecar 不需要再拉运行时镜像,可在 .env 加 SANDBOX_PULL_ON_START=0 跳过启动时的拉取尝试。
方式 C:推到自己的私有仓库(阿里云 ACR、Harbor 等)——docker tag 后推上去,.env 设 IMAGE_REGISTRY=registry.cn-xxx.aliyuncs.com、IMAGE_OWNER=<你的命名空间>。
第三步:基础镜像加速(可选)
postgres / redis / qdrant 来自 Docker Hub,访问慢时给 Docker 守护进程配加速器(/etc/docker/daemon.json):
{ "registry-mirrors": ["https://docker.m.daocloud.io"] }
改完 sudo systemctl restart docker,然后正常 docker compose -f docker-compose.prod.yml up -d 即可。
資源提案
| シーン | CPU | メモリ | 説明 |
|---|---|---|---|
| 利用可能な最小値(試用、単位ユーザー) | 2 核 | 4 GB | 完全なスタックは走ることができますが、サンドボックスは並行して緊張する |
| 従来のチーム使用 | 4 核 | 8 GB | 日常 セッション + RAG + 少量の並行コード実行 |
| コード実行 / ディプリサーチ 重度の使用 | 4 核以上 | 16 GB 以上 | サンドボックスに並行して増加する |
サンドボックスのメモリコストの推定:セッションごとの容器の上限SANDBOX_MEMORY(デフォルト 2g) 同期上限SANDBOX_MAX_SESSIONS(デフォルト 16) 極端な場合、サンドボックスだけが 32 GB を消費する可能性があります。.envこの2つの値を下げる(例えば、SANDBOX_MAX_SESSIONS=4サンドボックス コントローラ自体は最大 1 GB (mem_limit)。
ディスクに関しては、pgdata、qdrantdata 和 DATA_DIR使用量が増加するにつれて、SSDに置き、モニタリングを予約することをお勧めします; Qdrant ベクトルとアップロードファイルは、通常、最も急速に成長する 2 つのブロックです。
日記チェック
cd deploy
# 跟踪 app 日志(最常用)
docker compose -f docker-compose.prod.yml logs -f app
# 只看最近 200 行
docker compose -f docker-compose.prod.yml logs --tail=200 app
# 看最近一小时所有服务的日志
docker compose -f docker-compose.prod.yml logs --since=1h
# 单看沙箱(排查代码执行问题时)
docker compose -f docker-compose.prod.yml logs -f sandbox
# 查看某个容器健康检查的探测输出
docker inspect --format='{{json .State.Health}}' aivory-app-1 | jq
通常の運用命令
cd deploy
# 查看各服务状态(重点看 STATUS 列的 healthy / unhealthy)
docker compose -f docker-compose.prod.yml ps
# 重启单个服务
docker compose -f docker-compose.prod.yml restart app
# 进入 Postgres 交互式命令行
docker compose -f docker-compose.prod.yml exec postgres psql -U aivory -d aivory
# 验证 Redis 连通性(密码从容器自身的环境变量读取)
docker compose -f docker-compose.prod.yml exec redis sh -c 'redis-cli -a "$REDIS_PASSWORD" ping'
# 打开 app 容器 shell
docker compose -f docker-compose.prod.yml exec app sh
# 从宿主机探测健康端点
curl -fsS http://localhost/api/health
# 查看各命名卷占用的磁盘
docker system df -v | grep aivory
psqlよく使われている自己検査のいくつか:
\dt -- 表清单
SELECT count(*) FROM users; -- 用户数
\q -- 退出
アップグレードコース
データベースの構造はapp起動時に自動移行し、アップグレード自体は「新しい鏡を引っ張る + コンテナを再構築する」ですが、アップグレードする前にバックアップしてください。:
cd deploy
# 1. 备份:在管理后台 Backup & Migration 导出全量备份,
# 并确认归档已生成在 ./data/backups/ 下(拷贝一份到异地更稳妥)
# 2. 拉取新镜像(IMAGE_TAG 固定了版本的话,先在 .env 里改成目标版本)
docker compose -f docker-compose.prod.yml pull
# 3. 滚动重建(只重建镜像有变化的容器)
docker compose -f docker-compose.prod.yml up -d
# 4. 验证
docker compose -f docker-compose.prod.yml ps
curl -fsS http://localhost/api/health
docker compose -f docker-compose.prod.yml logs --tail=100 app
IMAGE_TAG=latestつまり、毎回pull新しいバージョンが可能で、アップグレードタイムは制御されません。生産環境は、アップグレードを推奨します。IMAGE_TAG特定のバージョンタグに固定し、アップグレード時に明示的に値を変更します。IMAGE_TAG古いバージョンに戻るup -d(データベース構造の移行は前方に進み、バックアップが利用可能であることを確認してください。
よくある故障検査
| 症状 | たぶん原因 | 処理 |
|---|---|---|
docker compose up直接ミスset JWT_SECRET in .env(またはPOSTGRES_PASSWORD / REDIS_PASSWORD 類似のエラー) | .env必須変数、composeの欠如:?試験 遮断 | cp .env.example .env次に記入する。openssl rand -hex 32生成JWT_SECRET |
app起動と終了、ログメッセージ JWT_SECRET 問題 | AIVORY_ENV=production 下 JWT_SECRET少なくとも 32 文字でなければなりませんが、起動が拒否されます。 | 交換openssl rand -hex 32輸出後up -d |
app再起動、ログデータベース認証/接続失敗 | POSTGRES_PASSWORD特殊文字(@ : / # % &複製された接続 URL を破損させた場合;または$compose 変数の挿入値が消去されたか、またはパスワードを変更したが、pgdata古いコードは | コードは全用openssl rand -hex 24(純16進制、特殊文字なし) 初期化したライブラリのパスワード変更は psql 内で必要ALTER USER |
sandbox表示 unhealthy (特に最初の起動) | 冷たいスタートで、セッションの実行時刻を映像化するaivory-sandboxネットワーク速度は120s以上。start_period | 看 logs -f sandbox引き出しが継続しているかどうかを確認する;事前に確認できます。docker pull ghcr.io/hjxwz123/aivory-sandbox:latest;app影響を受けず、コードの実行のみが一時的に利用できません。 |
appQdrant 401 / 未承認 | app 的 QDRANT_API_KEYQdrant 側の key と一致しない (通常は外部の Qdrant クラスターで発生するか、キーの片側だけを変更する) | 両側に同じ値を設定し、スタック内で両側が同じ値を読み取る.env変数、完成up -d再建できる |
ポート 80 が占められ、app起きない | ホスト 80 には他のサービスがあります | compose 文書の変更"80:8787"左のポート |
| 会話は正常ですが、コード実行エラー | サンドボックスは柔らかい依存性app吊るされるからではない。 | チェックsandbox健康状態、/var/run/docker.sock利用可能か、利用可能か |
| RAG 検索効果が急激に悪化(全文の注入に劣化) | Qdrant が利用できず、または空っぽで、アプリケーションは自動で全文を戻します。 | チェックqdrant容器の状態とappログ、回復後自動でベータ回収 |
次のステップ
- リバースプロキシとHTTPS: スタックの前に TLS 終了層を追加します。
- Cloudflare アクセス: CDN セットの注意事項。
- コード サンドボックス 展開: サンドボックスのセキュリティ モデル と深さの調節。
- 軽量デプロイ(SQLite)単機小規模シーンに対する極めて簡素な代替案。
- 初めての運用配置: 管理者を初期化し、モデルチャンネルを追加します。