◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-04-28中級

Rork で作るモバイルアプリの AI バックエンド:Cloudflare Containers と Workers AI の使い分け実践ガイド

RorkアプリのAI機能をCloudflare ContainersとWorkers AIでどう使い分けるかを解説します。まずWorkers AIで済むかを疑う判断、Containersを選ぶ3つの局面、最小構成での呼び出し、実際のコスト感覚、運用で分かった落とし穴まで具体的に扱います。

Cloudflare ContainersWorkers AIRork585AIバックエンドモバイルアプリ12GPU推論エッジAI4

Rork でアプリを作っていて「画像から何かを判定したい」「重めの推論モデルを動かしたい」となったとき、最初に直面するのが推論基盤の選択です。Workers AI は手軽ですが利用できるモデルが限定されます。一方で AWS や GCP に GPU インスタンスを立てるのは個人開発者には荷が重い。私はこの中間が長らく欲しいと思っていたのですが、2025 年に GA になった Cloudflare Containers でようやくその「ちょうどいい場所」が手に入りました。

ここではRork で生成した React Native コードから呼び出すことを前提に、Cloudflare Containers と Workers AI をどう使い分けるかを、私が実際に運用しているアプリの構成例とあわせてお話しします。

まず Workers AI で済ませられないかを疑う

Cloudflare Containers は便利ですが、まず最初に「本当に Containers が必要か」を考える価値があります。Workers AI で動くモデルがあるなら、ほぼ常にそちらの方が安く・速く・運用が楽です。

Workers AI で配信されている主なモデル系統には次のようなものがあります。

  • 一般的な対話・要約系:Llama 系、Mistral 系、Qwen 系
  • 画像生成:Flux、Stable Diffusion XL Lightning
  • Embedding:BGE 系、E5 系
  • 音声:Whisper(小サイズ)

たとえば「ユーザーが入力した日記を 3 行に要約する」「商品レビューの感情分類をする」程度なら、わざわざ Containers を使う理由はありません。Workers AI に投げて終わりです。

// app/api/summarize+api.ts (Rork が生成する Expo Router の API ルートに置く想定)
export async function POST(request: Request) {
  const { text } = await request.json();
 
  const ai = (request as any).env.AI; // Workers バインディング
  const result = await ai.run("@cf/meta/llama-3.1-8b-instruct", {
    messages: [
      { role: "system", content: "次の文章を日本語で3行に要約してください。" },
      { role: "user", content: text },
    ],
  });
 
  return Response.json({ summary: result.response });
}

このコードはモバイル側からの fetch で完結します。コールドスタートはほぼゼロ、料金も無料枠が大きく、Rork から呼び出す分にはこれで十分まかなえるケースが大半です。

ではどんなときに Containers の出番が来るのでしょうか。

Containers を選ぶべき 3 つの局面

私が実装の中で「Workers AI では無理だ」と判断したのは、次の 3 パターンでした。

第一に、独自の学習済みモデルを動かす場合です。たとえば自分でファインチューニングした YOLOv8 を使った商品検出、特定のキャラクター画像を識別する CoreML→ONNX 変換モデルなど、Workers AI のカタログにないモデルは Containers に乗せるしかありません。

第二に、推論前後の重い前処理・後処理がある場合です。画像のリサイズ・正規化、PDF からのテーブル抽出、複数画像の合成といった処理は、Workers の CPU 時間制限(無料 10ms / 有料 30 秒)にひっかかりやすく、Containers の方が安全です。

第三に、長時間ジョブが必要な場合です。動画の文字起こし、長文 PDF の解析、ベクトル DB への一括投入など、数十秒〜数分かかる処理は Containers + Queues の組み合わせが本命になります。

逆に言えば、これらに当てはまらない場合は Containers は不要です。「なんとなく強そうだから」で導入すると、コールドスタートと固定費の両方を背負うことになります。

Rork アプリから Containers を呼び出す最小構成

実装の話に進みます。Rork で作ったアプリから Containers を叩く際、私は以下のような薄い Worker をフロントに置く構成を取っています。

[Rork App] → [Cloudflare Worker (認証・課金チェック・キュー投入)] → [Containers (推論)]

Worker をフロントに置く理由は 3 つあります。認証トークンの検証、Stripe 等の課金ステータスのチェック、そしてジョブの非同期化です。Containers を直接モバイルから叩くこともできますが、本番アプリでは課金や不正利用対策を間に挟みたいので、Worker をプロキシにします。

wrangler.toml の Containers 定義は次のようになります。

name = "rork-ai-backend"
main = "src/index.ts"
compatibility_date = "2026-04-01"
 
[[containers]]
name = "yolo-detector"
image = "./containers/yolo/Dockerfile"
instance_type = "standard-2" # 2 vCPU / 4GB
max_instances = 5
 
[[ai]]
binding = "AI"
 
[[queues.producers]]
binding = "DETECT_QUEUE"
queue = "yolo-detect"

ポイントは instance_type の選び方です。GPU が必要な場合は gpu-* 系を選びますが、単価が大きく上がるので、まずは CPU 系で動くかを試すのを強くおすすめします。私のケースでは ONNX Runtime に変換することで CPU 系インスタンスで十分実用速度が出ました。

Worker 側のエントリポイントは次のようになります。

// src/index.ts
import { Container } from "@cloudflare/containers";
 
export class YoloDetector extends Container {
  defaultPort = 8080;
  sleepAfter = "30s"; // アイドル時間でスケールダウン
}
 
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
 
    if (url.pathname === "/api/detect" && request.method === "POST") {
      // 認証
      const userId = await verifyAuth(request, env);
      if (!userId) return new Response("Unauthorized", { status: 401 });
 
      // 課金プランの確認(無料枠超過チェック)
      const allowed = await checkQuota(userId, env);
      if (!allowed) return new Response("Quota exceeded", { status: 402 });
 
      // Container にプロキシ
      const id = env.YOLO_DETECTOR.idFromName("singleton");
      const container = env.YOLO_DETECTOR.get(id);
      return container.fetch(request);
    }
 
    return new Response("Not Found", { status: 404 });
  },
};

sleepAfter = "30s" はコスト最適化の要です。アクセスが途切れた 30 秒後にコンテナが停止し、課金が止まります。次のリクエストではコールドスタートが発生しますが、私のアプリでは 2〜3 秒で立ち上がっています。リアルタイム性が必要な機能では sleepAfter を長めに設定するか、定期的に温めるリクエストを送る運用が必要です。

モバイル側(Rork 生成コード)からの呼び出し

Rork が生成するコードは React Native + Expo です。fetch 呼び出しは以下のように、認証トークンと共に送ります。

// app/(tabs)/detect.tsx の中で使うフック想定
async function detectObjects(imageUri: string, token: string) {
  const formData = new FormData();
  formData.append("image", {
    uri: imageUri,
    name: "photo.jpg",
    type: "image/jpeg",
  } as any);
 
  const res = await fetch("https://rork-ai-backend.example.workers.dev/api/detect", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
    },
    body: formData,
  });
 
  if (res.status === 402) {
    throw new Error("PLAN_REQUIRED");
  }
  if (!res.ok) {
    throw new Error(`Detection failed: ${res.status}`);
  }
  return res.json() as Promise<{ objects: Array<{ label: string; score: number }> }>;
}

PLAN_REQUIRED を別エラーで投げているのは、UI 側でメンバーシップ誘導モーダルを出し分けるためです。私はこの 402 を受けて Rork で作ったアプリの収益化完全ガイド で書いたサブスク導線につなげる設計にしています。

コスト感覚:実際にいくらかかるか

私が運用している小規模アプリ(DAU 約 200、推論回数月 1.5 万回)での月額は以下のような内訳でした。

  • Workers リクエスト:無料枠内
  • Containers 稼働時間:約 $4.20(standard-2、ピーク時のみ稼働)
  • R2 保存(推論前後の画像):約 $0.30
  • 合計:月 $5 弱

これは GPU を使わず、ONNX に変換した CPU 推論で済ませた場合の数字です。GPU インスタンスを使うとざっくり 5〜10 倍程度になりますので、本当に GPU が必要かは慎重に検証してください。

ちなみに同じ処理を AWS の g4dn.xlarge で常時稼働させた場合は月 $300 を超えるので、個人開発者にとって Containers のスケール・トゥ・ゼロは決定的なコスト優位性になります。バックエンド設計をもう一段深掘りしたい場合は Rork × Hono × Cloudflare Workers REST API 実装ガイド も参考になるはずです。

運用してみて分かった落とし穴

最後に、実際に本番運用する中でつまずいたポイントを 3 つだけ共有します。

ひとつ目は、コールドスタート中に来たリクエストの扱いです。Containers が停止状態から起動するまで、後続リクエストは待たされます。タイムアウトを Worker 側で 30 秒に設定しておかないと、モバイル側のデフォルトタイムアウト(多くの fetch ライブラリで 60 秒前後)が先に切れて、ユーザー側はエラーに見えるのに裏では推論が完了しているという状態になります。

ふたつ目は、ログの集約です。Containers のログはデフォルトでは Worker の console.log と別系統になります。私は Sentry を Containers 内のアプリにも仕込んで、Worker と Containers のエラーを同じダッシュボードで見られるようにしました。

みっつ目は、デプロイのアトミック性です。Worker の更新と Containers のイメージ更新は別操作になるため、新しい API スキーマを期待する Worker が古いイメージにつながる瞬間が発生します。私はこれをスキーマバージョンをパスに含めることで対処しています(/api/v2/detect のような形)。

さいごに

Cloudflare Containers は「Workers AI では足りない、でも GPU インスタンスを建てるほどではない」という個人開発者の隙間ニーズにぴたりとはまるサービスです。Rork で生成したアプリのバックエンドとしては、まず Workers AI で動かしてみて、限界を感じた機能だけを Containers に切り出すのが最もコスト効率の良いアプローチだと感じています。

いきなり全機能を Containers に乗せるのではなく、まずは画像分類のような限定機能でひとつ動かしてみてください。wrangler containers deploy を一度叩くだけで、自分のアプリが「AI バックエンドを持つ本格的なサービス」に化ける感覚は、一度体験するとやめられないと思います。

Workers の挙動を理解しておくと、Containers との境界設計がぐっと楽になります。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ 開発ツール2026-10-15
EXPO_PUBLIC_ の値が、開発では読めて本番だけ undefined になる書き方を、変換結果で見分けます
API の URL が開発中は読めるのに、ビルドした途端に undefined になる。原因は process.env の書き方でした。babel-preset-expo の変換結果を開発・本番で並べ、置き換わる書き方と残ってしまう書き方を表にしました。
⬡ 開発ツール2026-10-13
リリース前に、書き出した Expo バンドルの中から自分の鍵を探してみる
ソースに鍵がなくても、出来上がったアプリの中に鍵が残ることがあります。Expo SDK 57 で実際に expo export を実行し、バンドルの中身を grep と 60 行ほどの Node スクリプトで調べて、見つかる鍵と見つからない鍵の違いを確かめました。
⬡ 開発ツール2026-10-07
日本語の見出しだけ太字にならない — Android で fontWeight が効かないときの切り分けと直し方
Rork で作ったアプリで、iPhone では太字なのに Android では日本語の見出しが細いままになる症状の切り分け記事です。原因の見分け方、fontFamily を重みごとに分ける直し方、確認用コードを載せています。