RIDM Apps デプロイガイド

RIDM Apps は、つないだ GitHub リポジトリをビルド・デプロイし、<name>.ridm.ai で公開するホスティングです。申し込みはコンソールのマイアプリから、料金は料金ページでご確認ください。利用条件は利用規約 第3章(韓国語)で定めています。含まれる内容をざっと知りたい場合は、アプリホスティングの概要をご覧ください。

デプロイの流れ

  1. 申し込み — マイアプリでプランとアドレス(サブドメイン)を選びます。アドレスはあとから変更できません。
  2. GitHub 連携 — アプリ画面で GitHub App ridm-apps-builder をインストールし、リポジトリとブランチを選びます。リポジトリには読み取り権限だけが付与されます。
  3. Push — つないだブランチに push するたびにビルドが始まります。ビルド履歴と失敗ログはアプリ画面に表示されます。
  4. 初回デプロイ — ビルドが成功すると、自動チェックを経てすぐに公開されます(後述)。以降の push も自動でデプロイされます。

Dockerfile の要件

Next.js スターター

next.config.js に output: 'standalone' を追加し、下の Dockerfile をリポジトリのルートに置きます。public フォルダがない場合は、その行を削除してください。

FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build --chown=1000:1000 /app/public ./public
COPY --from=build --chown=1000:1000 /app/.next/standalone ./
COPY --from=build --chown=1000:1000 /app/.next/static ./.next/static
USER 1000
EXPOSE 3000
CMD ["node", "server.js"]

Next.js でなくてもかまいません。コンテナがポート 3000 で応答すれば、どんなスタックでも動きます。

実行環境

Cookie

ridm.ai の下には多くのパートナーアプリがあるため、ゲートウェイは Cookie を各アプリ自身のアドレスにひも付けます。アプリのコードではいつもどおり Cookie を使えます。ただし、次の点に注意してください。

データベース

アプリごとに専用の PostgreSQL データベースが付き、接続情報は環境変数で渡されます。DATABASE_URL(SSL 必須)に加え、PGHOST、PGPORT、PGDATABASE、PGUSER、PGPASSWORD、PGSSLMODE です。容量の上限はプランによって異なり、使用量はアプリ画面に表示されます。

データベース接続には、証明書を完全に検証する TLS を使います。認証局(Amazon RDS)の証明書はコンテナにあらかじめ入っているため(NODE_EXTRA_CA_CERTS、PGSSLROOTCERT)、コードに SSL オプションは不要です。new Pool({ connectionString: process.env.DATABASE_URL, max: 2 }) だけで十分です。接続はアプリごとに3つまでなので、プールサイズは2以下にしてください。

環境変数

コンソールでシークレットを追加(人が操作)

コンソールでは、人がどんな名前でも追加できます。AIツールでは設定できない名前(後述)も、この方法で追加します。

  1. console.hrum.ai にログインし、左メニューのマイアプリを開きます。
  2. アプリカードを下にスクロールし、環境変数セクションまで移動します。保存済みの名前には、値の代わりに •••••••• が表示されます。
  3. 1つ目の欄名前(例:STRIPE_SECRET_KEY)に名前を入力します。小文字やその他の文字は、入力中に大文字とアンダースコアに変わります。
  4. 2つ目の欄値に値を貼り付けます。この欄はパスワード欄のように伏せ字で表示されます。
  5. 保存をクリックすると、変数が保存され、まもなくアプリが再起動するという確認が表示されます。同じ名前で再度保存すると、値が変わります。
  6. 削除するには、その行の削除をクリックし、確認ダイアログで削除をクリックします。削除するとアプリが再起動し、値は復元できません。

値を移すときは、次の点に注意してください。

AIでシークレットを追加(AI運用トークン)

AIコーディングツールに環境変数を設定させるには、AI運用トークンが必要です。これは、発行時に人が hosting_ops 権限にチェックを入れたコンソール管理トークン(hrum_cm_…、AIでコンソールを管理を参照)です。この権限があれば、ツールは作業ごとに同意を求め直しません。環境変数の設定は HRUM MCP には含まれていないため、値が AI モデルを通ることはありません。下の HTTP パスを使ってください。

AI経路で設定できる名前は、末尾が _KEY・_SECRET・_TOKEN・_PASSWORD・_PASSWD・_PASS・_SALT・_CLIENT_ID のものだけです(例:STRIPE_SECRET_KEY、MASTER_KEY)。その中でも、実行環境や接続先アドレスを変える名前は拒否されます。末尾が _URL・_URI・_ENDPOINT・_HOST・_PROXY の名前、先頭が NODE_・LD_・DYLD_・PYTHON・JAVA_・_JAVA_・JDK_・RUBY・PERL・GIT_・NPM_CONFIG_・GLOBAL_AGENT_・AWS_ENDPOINT_URL の名前、そして HTTP_PROXY・HTTPS_PROXY・NODE_TLS_REJECT_UNAUTHORIZED・SSL_CERT_FILE・PATH・OPENAI_API_BASE などの名前です。それ以外の名前(例:ADMIN_PATH、SMTP_HOST)は、人がコンソールで追加します。削除はAI経路でもできます(予約済みの名前を除く)。

トークンの取得 — 有効期間は30日です。

  1. AIは認証なしで POST https://api.hrum.ai/api/v1/partners/v1/console-ai/consents に {"action":"token_issue","payload":{"scopes":["hosting_ops"],"label":"My app ops"}} を送ります。レスポンスの data には consentUrl、userCode(8文字)、code、pollSecret が入っています。
  2. AIがユーザーに見せるのは consentUrl と8文字のコードだけです。pollSecret は見せません。
  3. 10分以内に、人がコンソールにログインしたブラウザでリンクを開き、AIツールの画面に出ている8文字のコードを確認コード欄に入力し、hosting_ops にチェックを入れて同意をクリックします。先にチェックボックスの下の警告を読んでください(トークンが漏れると、第三者が環境変数の上書き、デプロイ、再起動を行えるおそれがあります)。
  4. AIが X-Consent-Poll-Secret ヘッダーを付けて GET …/console-ai/consents/{code} を呼び出すと、承認後の最初のレスポンスに限り、data.token.value にトークンが1回だけ入ります。画面に出さず、そのままシークレットストア(キーチェーン、権限 600 のファイルなど)に保存してください。
  5. 不要になったら、コンソールのAPIキー画面にある「AIコンソール管理トークン」で取り消しをクリックします。すぐに使えなくなります。有効なトークンは、アカウントごとに10個までです。

呼び出し — ベース URL は https://api.hrum.ai、ヘッダーは Authorization: Bearer hrum_cm_… です。アプリ ID は、GET /api/v1/partners/v1/console-ai/hosting のレスポンスにある data.apps[].id(UUID)です。

作業リクエストレスポンス
名前の一覧GET /api/v1/partners/v1/console-ai/hosting/apps/{appId}/env{"success":true,"data":{"env":{"names":[…]}}} — 値は含まれません
追加・変更PUT …/hosting/apps/{appId}/env/{NAME}、本文 {"value":"…"}更新後の名前の一覧。アプリが再起動します
削除DELETE …/hosting/apps/{appId}/env/{NAME}更新後の名前の一覧。アプリが再起動します

例(bash または zsh)。値とトークンはコマンドライン引数として渡さず、値は標準入力から渡します。$APP_ID とキーチェーンの項目名は、ご自身のものに置き換えてください。

# 1) Read secrets into shell variables (nothing is printed)
HRUM_CM_TOKEN=$(security find-generic-password -s hrum-console-ops -w)
VALUE=$(security find-generic-password -s myapp-STRIPE_SECRET_KEY -w)

# 2) Value goes through stdin, token through a header file: neither shows up in ps or shell history
printf '%s' "$VALUE" | jq -Rs '{value: .}' | curl -s -X PUT \
  -H @<(printf 'Authorization: Bearer %s' "$HRUM_CM_TOKEN") \
  -H 'Content-Type: application/json' --data-binary @- \
  "https://api.hrum.ai/api/v1/partners/v1/console-ai/hosting/apps/$APP_ID/env/STRIPE_SECRET_KEY" \
  | jq '.data.env.names // .'

# 3) Forget the value
unset VALUE

保存されると、レスポンスには名前の一覧だけが表示されます。アプリのアドレスを開き、新しい値で再起動したことを確認してください。

AIでデプロイ・ログ確認(AI運用トークン)

同じAI運用トークン(hosting_ops)で、サイトを開かずにデプロイを開始し、デプロイ状況やアプリが出力したログを確認できます。

HRUM MCP 0.3.3 以降では、MCP ツールでも同じことができます。hrum_hosting_deploy(デプロイ開始)、hrum_hosting_deploys(デプロイ状況)、hrum_hosting_logs(実行ログ)です。先に hrum_ai_connect で hosting_ops 権限付きのトークンを取得し、hrum_hosting_status でアプリ ID を確認してください。MCP を使わないツールは、下の HTTP パスを使います。

作業リクエストレスポンス
デプロイ開始POST …/hosting/apps/{appId}/deploy、本文 {} または {"commit":"40-char sha"}{"deployId","commitSha","retried","status":"queued"}。コンソールの「今すぐデプロイ」と同じく、つないだブランチの最新コミットをビルドします(初回デプロイの審査とプランのルールも同じ)。commit はその最新コミットの場合だけ受け付けます。古いコミットにはまだ対応していません(400 COMMIT_NOT_BRANCH_HEAD)。1分に2回まで
デプロイ状況GET …/hosting/apps/{appId}/deploys?limit=20新しい順:id、commitSha、status、queuedAt、startedAt、finishedAt、liveAt、failReason、scanFindings(シークレットスキャンで検出されたファイル・行・ルールのみ)、logTail(ビルドログの末尾)、startFailReason、startFailLog
ログの要求POST …/hosting/apps/{appId}/logs、本文 {"lines":100}(1–200)202 {"requestId"}
ログの取得GET …/hosting/apps/{appId}/logs/{requestId}status が pending なら2–3秒後に再試行し、delivered なら lines を読みます。3分を過ぎると expired になります

ログと失敗理由は、返す前にマスクされます。そのアプリに保存された環境変数の値や、sk-…、AKIA…、Bearer …、JWT、…_live_… のようなキー形式の文字列は [redacted] になります。それでも、まずはアプリがシークレットをログに出さないようにすることが大切です。すべての呼び出し(拒否されたものも含む)は、トークン・アプリ・結果とともに記録されます。

コンソールのログインセッションや HRUM MCP のログインセッション向けにも、同じパスがあります。POST …/console/hosting/apps/{appId}/deploy-now(MCP では新規登録で作られたセッションのみ)、GET …/console/hosting/apps/{appId}/deploys、POST …/console/hosting/apps/{appId}/logs、本文 {"lines":100}(1–200、MCP では新規登録で作られたセッションのみ)→ GET …/logs/{requestId} です。自分のアプリにだけ使え、ログの行は同じルールでマスクされます。取得したログの内容は、受け渡し(または要求)から24時間後に削除されます(2026-12-01からは7日、規約 第20条第5項)。

HRUM_CM_TOKEN=$(security find-generic-password -s hrum-console-ops -w)
API=https://api.hrum.ai/api/v1/partners/v1/console-ai/hosting/apps/$APP_ID
git push origin main
curl -s -X POST -H @<(printf 'Authorization: Bearer %s' "$HRUM_CM_TOKEN") \
  -H 'Content-Type: application/json' -d '{}' "$API/deploy" | jq '.data // .'
curl -s -H @<(printf 'Authorization: Bearer %s' "$HRUM_CM_TOKEN") \
  "$API/deploys?limit=3" | jq '.data.deploys[] | {id, commitSha, status, liveAt, failReason}'

シークレットの安全ルール(AIツール向け)

実際の例:販売管理アプリを作っていたAIツールが、アプリのマスターキーを環境変数に入れようとしましたが、当時はAI向けの運用経路がなかったため、そこで止まりました。現在は、MASTER_KEY のように末尾が _KEY の名前はAI運用トークンで設定でき、それ以外の名前は人がコンソールで追加します。どちらの場合も、値がチャットを通ることはありません。

環境変数のエラー

エラーの本文は {"success":false,"statusCode":…,"message":"…"} の形です。環境変数のエラーには code がないため、下のステータスコードとメッセージの冒頭で見分けてください。API はこれらのメッセージを韓国語で返すため、各メッセージの冒頭を返されたとおりに示し、そのあとに意味を添えています。メッセージの全文は llms.txt にあります。

ステータスメッセージの冒頭原因 → 対処
400이름은 대문자·숫자·밑줄만(名前は英大文字・数字・アンダースコアのみ)名前がルールに反しています → 英大文字・数字・アンダースコアのみ、数字で始めない、最大64文字
400플랫폼이 쓰는 이름이라(プラットフォームが使う名前)PORT、HOSTNAME、NODE_OPTIONS、DATABASE_URL、または RIDM_ / PG で始まる名前 → 別の名前を使ってください
400AI 연결이 자동으로 넣는 이름이라(AI連携が自動で設定)HRUM_API_KEY → このアプリはAI連携がオンのため、プラットフォームが設定します。自分で設定するには、AI連携をオフにしてください
400이 계정에서 발급한 API 키만 넣을 수 있어요(このアカウントで発行したキーのみ)ご自身のコンソールの「APIキー」で発行したキーを使ってください
400AI 연결이 앱마다 만든 키는 넣을 수 없어요(AI連携のアプリ別キーは不可)コンソールの「APIキー」で発行したキーを使ってください
400값을 입력해 주세요(値を入力してください)値が空です
400값에 줄바꿈을 넣을 수 없(値に改行は使えません)複数行の値(PEM 形式の秘密鍵など) → base64 の1行に変換し、アプリ側でデコードしてください
400값은 2048바이트까지(値は2048バイトまで)値が2KBを超えています → 大きな値はデータベースや外部のシークレットストアに保管してください
400실행 환경을 바꾸는 이름이라 AI 경로(実行環境を変える名前のためAI経路では不可)AI経路で拒否される名前 → 人がコンソールで追加します
400AI 경로(AI 운영 토큰·MCP)로는 _KEY(AI経路で使えるのは _KEY… のみ)シークレット名の末尾ではありません → 名前を変えるか、人にコンソールで追加してもらってください
400Validation failed (uuid is expected)アプリ ID が UUID ではありません → GET /console-ai/hosting の apps[].id を使ってください
401MGMT_TOKEN_INVALIDトークンがない、期限切れ、または取り消し済みです → 新しく発行してください
403MGMT_SCOPE_DENIED (scope: "hosting_ops")トークンに hosting_ops がありません → チェックを入れて新しく発行してください
404앱을 찾을 수 없(アプリが見つかりません)他のアカウントのアプリ、または削除済みのアプリです → アプリ ID を確認してください
404없는 환경변수(存在しない環境変数)削除しようとした名前がありません → 名前の一覧を確認してください
409해지된 앱은 환경변수를 바꿀 수 없(解約したアプリ)アプリが解約手続き中です → 変更できません
409환경변수는 앱당 50개까지(アプリごとに50個まで)使っていない名前を削除してから再試行してください
429—追加と削除は1分に30回までです → 少し待ってから再試行してください
503환경변수 저장소가 준비되지 않(変数ストアの準備ができていません)サーバー側の問題です → 少し待ってから再試行し、続く場合はお問い合わせください

AI連携

AI連携を使うと、アプリから四柱推命(サジュ)API と AI API を呼び出せます。プラットフォームが、このアプリ専用の APIキーを追加します。すべてのアカウントで使えます。

  1. オンにする — コンソールのアプリでアプリの詳細を開き、「AI連携を使う」をオンにします。アプリ専用のキーが作られて環境変数 HRUM_API_KEY として追加され、アプリが再起動します。
  2. 呼び出す — アプリのサーバーコードで process.env.HRUM_API_KEY を読み、X-Partner-Api-Key ヘッダーに入れます。キーの値はコンソールでも表示されません。ブラウザに送られるコードには入れないでください。

オンの間は、HRUM_API_KEY を自分で設定できません。自分で値を設定していた場合は、AI連携をオンにする前に削除してください(削除しないと、既存の HRUM_API_KEY 環境変数を先に削除するよう求めるエラーが出ます)。このキーの利用料はクレジットから差し引かれ、アプリの詳細で設定した1日の利用上限に達すると、その日は止まります。オフにするとキーはすぐに無効になり、再びオンにすると新しいキーが作られます。このキーでは決済APIを呼び出せません。アプリで決済を受けるには、コンソールで発行したキーを使います。

アプリで決済を受ける

HRUM決済の決済ページを通じて、アプリのユーザーから決済を受けられます。販売者(個人、個人事業者、法人)として登録し、承認されると販売を始められます。販売者登録はコンソールの精算で行います。条件は利用規約 第32–39条(韓国語)で定めています。無料プランのアプリでも販売できます。ただし、月間転送量の上限に達した、または停止されたためにアプリが配信されていない間は、新しい決済の開始が 409 APP_NOT_SERVING で拒否されます。すでに開始した決済、注文の照会、返金は引き続き使えます。

  1. APIキーを入れる — コンソールのAPIキーで発行したキーを環境変数(例:HRUM_PARTNER_KEY)に入れます。AI連携が追加する HRUM_API_KEY では決済APIを呼び出せません。キーはサーバーコードでだけ使い、ブラウザには絶対に送らないでください。
  2. 商品を登録する — POST /api/v1/partners/v1/storefront/skus、本文 {"appId","name","priceWon"}。決済は、ここで登録した価格でのみ行われます。
  3. 決済を開始する — サーバーから本文 {"appId","skuId","qty","returnUrl"} で POST /api/v1/partners/v1/storefront/checkouts を送り、レスポンスの checkoutUrl へユーザーを移動させます。returnUrl は、このアプリの https アドレス(<name>.ridm.ai またはつないだドメイン)でなければなりません。
  4. 結果を確認する — 支払いが終わると、ユーザーは returnUrl?orderId=… に戻ってきます。サーバーで GET /api/v1/partners/v1/storefront/orders/{orderId} を呼び出し、status が paid であることを確認してから商品を提供してください。URL の値だけを信用しないでください。

すべてのリクエストに X-Partner-Api-Key ヘッダーが必要です。返金は、注文番号を [email protected] にメールでお送りください。返金されると、注文の status が canceled または partial_canceled に変わります。

const res = await fetch("https://api.hrum.ai/api/v1/partners/v1/storefront/checkouts", {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-Partner-Api-Key": process.env.HRUM_PARTNER_KEY },
  body: JSON.stringify({ appId, skuId, qty: 1, returnUrl: "https://myapp.ridm.ai/paid" }),
});
const { checkoutUrl } = (await res.json()).data;

キーの権限ごとに呼び出せるもの

キーを作成するときに権限を選べます。選んだ権限の外のパスを呼び出すと 403 KEY_SCOPE_MISSING が返ります。権限を選んでいない既存のキーは、引き続きすべてのパスを呼び出せます。すべてのパスは OpenAPI ドキュメントにあります。

精算データAPI

コンソール → 精算画面と同じ数値を、サーバーから取得できます。キーで見られるのは自分のデータだけです。期間 from / to は韓国の日付(YYYY-MM-DD、両端を含む)で指定し、空欄なら当月になります。一度に366日までです。

const res = await fetch("https://api.hrum.ai/api/v1/partners/v1/settlement/summary?from=2026-11-01&to=2026-11-30", {
  headers: { "X-Partner-Api-Key": process.env.HRUM_PARTNER_KEY },
});
const { sales, payouts, payoutFee } = (await res.json()).data;

販売者向け Webhook

決済の完了、返金、支払いがあったときに、サーバーへ通知します。受け付けるのは https:// のアドレスだけで(ポート 443 と 8443)、プライベートアドレスや内部アドレスは登録できません。

  1. アドレスを登録する — PUT /api/v1/partners/v1/webhooks/endpoint、本文 {"url":"https://myapp.ridm.ai/hrum/webhook"}。レスポンスの secret(whsec_…)は、このとき1回しか表示されません。すぐにサーバーの環境変数 HRUM_WEBHOOK_SECRET に入れてください。新しく発行するには {"url": …, "rotateSecret": true} を送ります。
  2. イベント — order.paid(決済完了)、order.refunded(返金1件)、payout.scheduled(支払予定)、payout.completed(支払完了)、webhook.test(POST /webhooks/test)。本文は {"id","type","created","data"} で、購入者の情報は含みません。1つの出来事は同じ id で1回だけ作られるため、id で重複を除いてください。
  3. 署名を検証する — ヘッダーは Hrum-Signature: t=<unix seconds>,v1=<hex> です。v1 は、(t + "." + the raw body as received)をシークレットで計算した HMAC-SHA256 です。5分より古い t は拒否してください。
  4. 再試行 — 10秒以内に 2xx で応答しない場合、1分後、5分後、30分後、2時間後、12時間後に再試行します(最大6回)。配信記録は GET /webhooks/deliveries で確認し、POST /webhooks/deliveries/{eventId}/redeliver で再配信できます。
const crypto = require("node:crypto");
function verifyHrumWebhook(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(String(header || "").split(",").map((p) => p.trim().split("=")));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const want = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const got = Buffer.from(parts.v1 || "", "hex");
  return got.length === want.length && crypto.timingSafeEqual(got, want);
}
// Express: verify the raw body before parsing it as JSON
app.post("/hrum/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verifyHrumWebhook(raw, req.get("Hrum-Signature"), process.env.HRUM_WEBHOOK_SECRET)) return res.sendStatus(400);
  const event = JSON.parse(raw); // if event.id was already handled, just return 200
  res.sendStatus(200);
});

初回デプロイと自動チェック

最初のビルドが成功すると、自動チェックを経て自動で承認され、アドレスがすぐに公開されます。有料プランの料金は、最初のデプロイが公開されるまで差し引かれません。リポジトリにシークレット(キー、トークン、パスワード)が含まれている場合、ビルドは失敗します。新しい外部接続先や新しい危険なパターンが見つかった場合、またはリポジトリの所有者が変わった場合も、デプロイは自動で行われ、その変化は記録されます。自動承認とチェックの結果はすべて記録され、あとから運営者が確認し、問題があれば公開を停止することがあります。公開を停止した場合は、メールとアプリ画面で理由をお知らせします。禁止行為については規約 第26条(韓国語)をご覧ください。

料金と制限

各プランの料金・メモリー・データベース・月間転送量は料金ページにあります。有料プランの料金は、チャージしたクレジットから毎月差し引かれます。請求日に残高が足りないとアプリは停止し、チャージすると自動で再開します。月間転送量は、訪問者に送ったレスポンスとアプリが外部へ送るトラフィック(外部 API の呼び出し、アップロード、データベースへの書き込みを含む)の合計です。WebSocket では双方向のフレームが数えられます。上限に達すると、開いている WebSocket も 1008 で閉じられます。上限に達すると公開とアプリの両方が止まります(翌月1日に再開)。有料プランでは、アプリ画面で転送量ブロックを購入すれば数分以内に復旧します。80%に達したときと上限に達したときに、メールでお知らせします。

バックアップと解約

お問い合わせ:contact@float.do