デプロイガイド
Docker Compose でのデプロイをおすすめします。compose 構成には PostgreSQL(db)と Node アプリケーション(app)の 2 つのサービスが含まれます。アプリケーションのプロセスは、バックエンド API とビルド済みのフロントエンドのページを両方とも配信します。
アプリの自動デプロイはありません
アプリケーションは CI による自動デプロイを行いません。.github/workflows/ci.yml は main への push と Pull Request のときに lint、型チェック、テスト、検証ゲート、フロントエンドのビルドを実行するだけです。デプロイはサーバー上で手動で行います。更新の手順は後述します。
ドキュメントサイトは例外です。website/ の変更が main にマージされると、.github/workflows/docs.yml がビルドして GitHub Pages に公開します(リポジトリの Settings → Pages で Source を GitHub Actions に設定してください)。Pull Request ではビルドとリンク切れのチェックのみ行います。
アーキテクチャの概要
| コンポーネント | 説明 |
|---|---|
db | イメージは postgres:alpine。データベース名とユーザー名はどちらも castor_kit で、データはボリューム postgres_data に保存 |
app | リポジトリのルートにある Dockerfile からビルド。コンテナ内では 5000 番ポートで待ち受け、アップロードされたファイルはボリューム app_data(/app/data にマウント)に保存 |
イメージのビルドは 2 段階です。どちらの段階も node:22-bookworm-slim(glibc)をベースにします(sodium-native などのネイティブモジュールは glibc 向けのビルド済みバイナリしか提供していないため、Alpine は使えません)。第 1 段階では依存関係をインストールし、フロントエンド(Vite)とバックエンド(tsup)をビルドしてから、本番用の依存関係だけに絞り込みます。第 2 段階は実行用のイメージで、root 以外のユーザー(uid 10001)で動作し、/health を使ったヘルスチェックが設定されています。
コンテナの起動時、docker-entrypoint.sh は次の処理を順番に実行します。
node dist/setup-once.js:PostgreSQL の advisory lock で保護したうえで、データベースのマイグレーション、RBAC の増分同期、AI SQL 用読み取り専用アカウントcastor_kit_roの作成または更新を行います。デモモードでは、復元の時期が来ていればデモデータも復元します。複数のレプリカが同時に起動しても順番に実行されるだけで、結果は冪等です。node dist/main.js:サービスを起動します。
ビルド時に使うレジストリ
Dockerfile はビルド引数 NPM_REGISTRY のレジストリから依存関係をインストールします。デフォルトは https://registry.npmjs.org ですが、docker-compose.yml は中国本土のミラー https://registry.npmmirror.com を渡すため、compose でビルドするとデフォルトでこのミラーを使います。別のレジストリを使うには、.env.production で NPM_REGISTRY を設定してください(例:NPM_REGISTRY=https://registry.npmjs.org)。
方法 1:セットアップウィザード
git clone https://github.com/robeshell/castorjs.git
cd castorjs
bash scripts/setup.shウィザードは管理者パスワード、アクセスポート(デフォルトは 5000)、任意の AI 設定を尋ね、SECRET_KEY、POSTGRES_PASSWORD、POSTGRES_RO_PASSWORD をランダムに生成して .env.production に書き込みます。そのあとサービスをビルド・起動し、/health の準備が整うまで待ちます。
スクリプトは Docker の設定を変更しません。お使いのネットワークでイメージの取得や依存関係のインストールに失敗する場合は、Docker にレジストリミラーを自分で設定し、NPM_REGISTRY を設定してから(上のレジストリの説明を参照)もう一度実行してください。
方法 2:手動で設定する
1. .env.production を作成する
リポジトリのルートに .env.production を作成し、少なくとも次の変数を含めます(どれか 1 つでも欠けていると compose は起動を拒否します)。
SECRET_KEY=<十分な長さのランダムな文字列>
ADMIN_PASSWORD=<admin アカウントの初期パスワード>
POSTGRES_PASSWORD=<データベースのパスワード>
POSTGRES_RO_PASSWORD=<AI SQL 用読み取り専用アカウントのパスワード>任意の変数:
APP_PORT=5000 # ホスト側のポート。未設定の場合は 8080
NPM_REGISTRY=https://registry.npmjs.org # イメージのビルドに使う npm レジストリ。compose のデフォルトは https://registry.npmmirror.comメール、ファイル保存、アップロード制限、AI モデルはここに書く必要はありません。デプロイ後にログインし、「システム設定」ページで設定します。環境変数で固定したい場合はシステム設定で行う設定を参照してください。使用できるすべての変数は 設定 を参照してください。ランダムな文字列は openssl rand -base64 48 で生成できます。
2. ビルドして起動する
docker compose --env-file .env.production up -d --build初回のビルドには数分かかります。その後 http://<サーバーのアドレス>:<APP_PORT> にアクセスし、admin と ADMIN_PASSWORD でログインします。
compose コマンドには毎回 --env-file を付けること
compose がデフォルトで読み込むのは .env だけで、.env.production は読み込みません。--env-file .env.production を付けないと必須の変数が欠けるため、コマンドはそのままエラーになります。
ADMIN_PASSWORD が効くのは初回だけ
admin アカウントは存在しないときにだけ作成されます。初回起動後に ADMIN_PASSWORD を変更しても既存アカウントのパスワードは変わらないので、ログインしてから画面上で変更するか、リセット してください。
方法 3:Render + Neon(無料のデモ)
Render の無料 Web サービスでアプリを動かし、データは Neon の無料 PostgreSQL に保存します。公開のオンラインデモに向いています。リポジトリ直下の render.yaml に設定が用意されており、デモモードが有効になります。
- ログイン画面にデモアカウント(
admin/castor-demo)が表示され、ワンクリックでログインできます - システム管理は読み取り専用で、パスワードも変更できません。コンポーネント例のデータは自由に追加・編集・削除できます
- サンプルデータは 24 時間ごとに自動で復元されます
無料プランの制限
執筆時点の無料枠です。登録する前に各サービスの公式サイトで確認してください。
- Render の無料インスタンスは 15 分間アクセスがないとスリープし、次のアクセスでは起動に数十秒かかります。スリープ中は定期タスクも実行されません
- Render の無料インスタンスのディスクは再起動やスリープで消去されるため、既定の
localドライバーで保存したファイル(コンポーネントサンプルでアップロードした画像・添付ファイル)も失われます。デモデータはもともと定期的に復元されるので、render.yamlではlocalのまま 1 ファイル 2MB までにしています。ファイルを残したい場合は、Render の Environment にSTORAGE_DRIVER=s3と Cloudflare R2 バケットのS3_*設定を追加してください(デモモードではシステム設定ページが読み取り専用のため環境変数を使います。ファイル保存とアップロードを参照) - Neon の無料データベースはアイドル時にコンピュートを停止し、次の接続で自動的に再開します
1. Neon でデータベースを作成する
- Neon に登録してプロジェクトを作成します。リージョンは AWS US East 2 (Ohio) を選び、
render.yamlにある Render サービスのregion: ohioと揃えます。別のリージョンにする場合も両方を同じにしてください - プロジェクトのダッシュボードで Connect をクリックし、「Connection pooling」をオフにして、直接接続の接続文字列をコピーします。形式は
postgresql://<ユーザー>:<パスワード>@ep-xxx.<リージョン>.aws.neon.tech/neondb?sslmode=requireです。ホスト名に-poolerを含めないでください。含まれていると AI データ検索がエラーになるので、ホスト名から-poolerを削除してください
直接接続を使う理由
起動時の初期化(マイグレーション、RBAC の同期、デモデータの復元)は、並行実行を防ぐためにセッション単位の advisory lock を使います。トランザクション単位のコネクションプールではこのロックを保持できません。接続文字列の channel_binding=require は残しても削除してもかまいません。
2. Render にデプロイする
- GitHub アカウントで Render に登録します。リポジトリが自分のアカウントにない場合は、先に Fork してください
- Render のダッシュボードで New → Blueprint を選び、リポジトリを選択します。Render が
render.yamlを読み込みます - 画面の案内に従って
DATABASE_URL(前の手順でコピーした接続文字列)を入力します。AI_API_KEYとAI_MODELも尋ねられますが、空のままにして AI を使うときに設定できます(後述の手順 4 を参照)。そのほかの変数はrender.yamlで設定済みか、自動生成されます - Apply をクリックします。初回のビルドには 5〜10 分ほどかかります。ステータスが Live になったらサービスの URL(
https://<サービス名>.onrender.com)を開くと、ログイン画面にデモアカウントが表示されます
README の Deploy to Render ボタンからでも同じようにデプロイできます。
3. その後の運用
render.yamlでは自動デプロイを無効にしていません。main に push するたびに Render が再ビルドし、ビルドに失敗するとメールで通知されます。不要な場合はサービスの Settings → Build & Deploy で Auto-Deploy をオフにしてください- デモアカウントのパスワードは
render.yamlのADMIN_PASSWORDです。アカウントを初めて作成するときにだけ使われるので、変更する場合は最初のデプロイ前に変えてください - デモデータをすぐに復元するには、ローカルのチェックアウトで Neon の接続文字列を指定して
pnpm demo:resetを実行します(例:DEV_DATABASE_URL='<Neon の接続文字列>' pnpm demo:reset。スクリプトは現在のNODE_ENVのデータベースを使い、開発環境ではDEV_DATABASE_URLです)。有料の Render インスタンスでは、サービスの Shell でnode dist/demo-reset.jsを実行することもできます。無料インスタンスには Shell がありません
4. AI を接続する(任意)
デモでは Google Gemini の無料枠を使って、AI チャットと AI データ検索を試せます。
- Google AI Studio で Google アカウントを使って API キーを作成します
- Render サービスの Environment で
AI_API_KEY(作成したキー)とAI_MODEL(おすすめはgemini-3.5-flash)を設定します。AI_API_BASEはrender.yamlで Gemini の OpenAI 互換エンドポイントに設定済みです。保存するとサービスが自動で再起動します
モデルの選び方
最新の Flash モデルは、無料枠では負荷が高く 503 を返すことがよくあります(執筆時点の gemini-3.8-flash など)。デモ環境では、gemini-3.5-flash や gemini-3.5-flash-lite のように少し前に出た安定版をおすすめします。エラーになったときは、Render の Logs で「AI 上游返回错误」を検索すると、アップストリームが返した理由を確認できます。
デモモードでは AI の呼び出しを制限します。IP ごとに 1 時間 20 回、サイト全体で 1 日 300 回、1 回の入力は 4000 文字まで、さらに返答の長さも制限します。DEMO_AI_* の変数で調整できます(設定を参照)。無料枠のリクエストはサービス提供者の製品改善に使われる場合があるため、デモ環境には機密情報を入力しないでください。
起動時に読み取り専用アカウントを作成できない
起動時に、AI データ検索で使う読み取り専用アカウント castor_kit_ro を作成します。Neon で作成が拒否される場合は、Neon の SQL Editor で次を実行してください。
CREATE ROLE castor_kit_ro LOGIN PASSWORD '<Render の POSTGRES_RO_PASSWORD の値>';そのあと Render で再デプロイします。初期化の処理が既存のアカウントのパスワードを更新し、権限を付与します。
Render で本番環境を動かす場合
DEMO_MODE を false にし、ADMIN_PASSWORD を強力なパスワードに変更してください。ただし無料インスタンスはスリープし、定期タスクも時間どおりには実行されず、ディスクも消去されます(アップロードファイルには s3 ドライバーが必要です)。本番で使う場合は有料インスタンスを選ぶか、方法 1 または方法 2 で自分のサーバーにデプロイすることをおすすめします。
よく使う運用コマンド
docker compose --env-file .env.production ps # サービスの状態を表示
docker compose --env-file .env.production logs -f app # アプリケーションのログを表示
docker compose --env-file .env.production restart app # アプリケーションを再起動
docker compose --env-file .env.production down # サービスを停止(データボリュームは保持)
curl -f http://localhost:<APP_PORT>/health # ヘルスチェック/health は、データベースが利用可能であれば { status: 'healthy', ... } を返し、そうでなければ 500 を返します。
down -v を安易に使わないこと
docker compose down -v はデータボリュームを削除するため、データベースとアップロードされたファイルがすべて失われます。
管理者パスワードのリセット
admin アカウントに新しいパスワードを設定するには(パスワードを忘れた場合など)、.env.production の ADMIN_PASSWORD に新しいパスワードを書き、コンテナを作り直して値を読み込ませてから、コンテナ内でリセットスクリプトを実行します。
docker compose --env-file .env.production up -d
docker compose --env-file .env.production exec app node dist/reset-admin-password.js変更されるのは admin のパスワードだけで(そのアカウントがスーパー管理者ロールを持つことも確認します)、ほかのアカウントには影響しません。ログインの失敗が続いてロックされている場合は、ロックが解けるまで待ってください(デフォルトは 15 分)。Docker を使わない場合は、API ディレクトリで本番環境として node dist/reset-admin-password.js を実行するか、ソースのディレクトリで pnpm seed:rbac -- --incremental --reset-admin-password を実行してください。
更新の手順
git pull
docker compose --env-file .env.production up -d --buildcompose は最新のコードでイメージを再ビルドし、app コンテナを作り直します。コンテナの起動時には新しいデータベースマイグレーションと RBAC の増分同期が自動で実行されます。新しいメニューは自動で表示されてスーパー管理者に付与され、既存のユーザー、ロール、独自のデータが消えることはありません。
更新の前にデータベースをバックアップしておくことをおすすめします。手順は後述します。
データの永続化とバックアップ
| ボリューム | デフォルトの名前 | 内容 |
|---|---|---|
postgres_data | castor-kit_postgres_data | PostgreSQL のデータ |
app_data | castor-kit_app_data | アップロードされたファイル(local ドライバー。s3 ドライバーではオブジェクトストレージに保存) |
ボリューム名は .env.production の COMPOSE_DB_VOLUME / COMPOSE_DATA_VOLUME で上書きできます(既存のボリュームを指定するなど)。
データベースのバックアップ例:
docker compose --env-file .env.production exec db pg_dump -U castor_kit castor_kit > castor_kit_backup.sqlPostgreSQL のバージョンを固定する
docker-compose.yml で db が使うイメージタグは postgres:alpine なので、新しいマシンで pull すると最新のメジャーバージョンが取得されます。PostgreSQL のデータディレクトリはメジャーバージョンをまたいでそのままは使えないため、本番環境ではタグを特定のメジャーバージョンに固定することをおすすめします。
リバースプロキシと HTTPS
本番環境では、アプリケーションの前段にリバースプロキシ(Nginx など)を置いて TLS を処理することをおすすめします。次の点に注意してください。
Hostとプロトコルのヘッダーを転送する:アプリケーションはプロキシを 1 段まで信頼し、X-Forwarded-For/X-Forwarded-Protoからクライアントの IP とプロトコルを取得します。COOKIE_SECUREが空の場合は、リクエストのプロトコルに応じて cookie にSecureフラグを付けるかどうかを自動で決めるため、X-Forwarded-Protoを正しく渡す必要があります。- WebSocket:
/wsパスではUpgradeヘッダーを転送する必要があります。WebSocket のハンドシェイクではOriginがHostと同一オリジンであること(またはCORS_ORIGINSの許可リストに含まれていること)を検証するため、プロキシは元のHostを保持しなければなりません。 - リクエストボディのサイズ:アプリケーションが許可するリクエストボディの上限はデフォルトで 16MB(
BODY_LIMIT)、インポートファイルの上限は 5MB です。Nginx のclient_max_body_sizeはデフォルトで 1MB しかないため、それに合わせて大きくする必要があります。 - ストリーミングレスポンス:AI チャットは SSE を使います。アプリケーションはレスポンスヘッダーに
X-Accel-Buffering: noを設定して、Nginx のバッファリングを無効にしています。
Nginx の設定例(APP_PORT=5000 の場合):
server {
listen 80;
server_name example.com;
client_max_body_size 16m;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}HTTPS の証明書は Let's Encrypt(Certbot の Nginx プラグインなど)で取得できます。HTTPS を有効にしたら、.env.production で COOKIE_SECURE=true を明示的に設定することもできます。
プロキシ経由でのみアクセスさせる
リバースプロキシを使う場合は、docker-compose.yml のポートマッピングをローカルホストだけにバインドするよう変更する(例:"127.0.0.1:${APP_PORT:-8080}:5000")と、プロキシを経由しない直接アクセスを防げます。
複数レプリカと定期タスク
デフォルトでは app コンテナは 1 つだけで、定期タスクのスケジューラーは web プロセス内で動作します(compose での RUN_SCHEDULER_IN_WEB のデフォルトは true)。
アプリケーションのレプリカを複数にする場合:
- web のレプリカに
RUN_SCHEDULER_IN_WEB=falseを設定します。 - スケジューラーのプロセスを別途実行します。同じイメージを使い、entrypoint を上書きして(
commandではありません)node dist/worker.jsにします。
イメージの ENTRYPOINT は docker-entrypoint.sh で、常に setup-once を実行してから main.js を起動し、command は読み込みません。そのため、docker-compose.yml にスケジューラーのサービスを追加するときは次のように書きます。
worker:
build: .
restart: unless-stopped
entrypoint: ["node", "dist/worker.js"]
environment:
# Same variables as the app service (DATABASE_URL, SECRET_KEY, ADMIN_PASSWORD, AI_SQL_DATABASE_URL, ...)
depends_on:
db:
condition: service_healthyスケジューラーのサービスは setup-once を実行しません。データベースの初期化は引き続き app コンテナが行います。
スケジューラーはデータベースのリースに基づいて動作するため、同じタスクを同時に取得できるのは 1 つのプロセスだけです。複数のプロセスが同時にスケジューラーを動かしても、タスクが重複して実行されることはありません。setup-once は advisory lock を使うので、複数のレプリカが同時に起動しても安全です。
対象範囲とプロセス内の状態
Castor は中小規模の業務システム(管理画面、社内ツール、B2B コンソール)を対象としており、必要なのは PostgreSQL だけで、Redis は不要です。セッション、ログインロック、定期タスク、webhook の再送はすべてデータベースに保存されるため、どのレプリカも同じ状態を参照します。ほとんどのデプロイは app コンテナ 1 つで足ります。可用性や CPU のためにレプリカを増やす場合は、先に次の点を確認してください。
- レート制限はプロセスごとに数えます。 N 個のレプリカをロードバランサーの後ろに置くと、実際の上限はシステム設定の値の最大 N 倍になります。ログインロックはデータベースで数えるため影響を受けません。
- ローカルのファイルストレージには共有ボリュームが必要です。
localストレージドライバーを使う場合、すべてのレプリカが同じ/app/dataボリュームをマウントする必要があります。そうでなければシステム設定で S3 に切り替えてください。 - すべてのレプリカで同じ
SECRET_KEYを使ってください。 そうしないと、あるレプリカが発行したセッション cookie が他のレプリカで拒否されます。 - パフォーマンスモニターのページには、ブラウザーが接続しているレプリカの情報が表示されます。
Docker を使わずにデプロイする
Node 22+、pnpm、PostgreSQL 14+ が必要です。
# 1. 依存関係をインストールしてビルドする
corepack enable
pnpm install --frozen-lockfile
pnpm build
# 2. リポジトリのルートまたは apps/api/ に .env.production を作成し、少なくとも次を含める:
# DATABASE_URL、SECRET_KEY、ADMIN_PASSWORD、POSTGRES_RO_PASSWORD
# 3. データベースを初期化する(マイグレーション + RBAC の増分同期 + 読み取り専用アカウント)
NODE_ENV=production node apps/api/dist/setup-once.js
# 4. サービスを起動する(デフォルトは 0.0.0.0:5000 で待ち受け。PORT で変更可能)
NODE_ENV=production node apps/api/dist/main.jsNODE_ENVは必ずコマンドライン(またはプロセスマネージャー)で設定してください。バックエンドはこれに基づいて.env.productionを読み込むかどうかを決めます。- 手順 3 は
POSTGRES_RO_PASSWORDを使って読み取り専用アカウントcastor_kit_roを作成するため、DATABASE_URLのアカウントにはロールを作成する権限が必要です。AI_SQL_DATABASE_URLが未設定の場合、AI データ検索はDATABASE_URLのアカウントをこのアカウントに置き換えた接続を使います。別の接続を使いたいときだけAI_SQL_DATABASE_URLを設定し、読み取り専用アカウントを指すようにしてください(例:postgresql://castor_kit_ro:<POSTGRES_RO_PASSWORD>@<host>/<データベース名>)。 - フロントエンドのビルド成果物は
apps/web/dist/にあり、バックエンドはデフォルトでここからページを配信します。 main.jsは systemd や pm2 などのプロセスマネージャーで管理することをおすすめします。独立したスケジューラーのプロセスはapps/api/dist/worker.jsです。- 更新時の手順:
git pull→pnpm install --frozen-lockfile→pnpm build→ 手順 3 を再度実行 → サービスを再起動。
