Share
Embed
Deploying Full-Stack Bun Applications on Cloudflare
「Cloudflare Workers Tech Talks in Fukuoka #1」で発表した資料になります。
https://workers-tech.connpass.com/event/365623/
## 内容
- BunをCloudflareで動かしたかったわけ
- Cloudflare Containersについて
- Cloudflare Containersでハマった話
daiki7nohe
November 12, 2025
Transcript
44 slides
-
自己紹介 名前: 浦田 大貴(Urata Daiki ) 所属: 株式会社Fusic 仕事: エンジニア
/ AI 活用推進 趣味: 個人開発 / OSS 推し: Durable Objects X: @daiki7nohe 2 -
フレームワークを自作中 Guren (紅蓮) https://github.com/gurenjs/guren フルスタックWeb フレームワーク Bun で動作 MVC パターン採用
Laravel やAdonisJS からインスパイア まだアルファ版 ロゴはHono をリスペクト 5 -
Guren - 特徴 すでにある優秀なライブラリを採用 Model: Drizzle ORM View: React +
Inertia.js によるAPI レスなSPA Controller/Routing: Hono Frontend Build/Testing: Vite 0→1 を高速に実装できることを目指す Bun のフルスタック性を活かせる(ランタイム、パッケージマネージャ、テストラン ナー、SQL 、S3 など) 6 -
Guren - コマンド bunx create-guren-app my-app bunx guren db:migrate bunx
guren make:migration --name add_posts_table bunx guren make:controller UserController bunx guren make:model User bunx guren make:view users/Index bunx guren make:test auth/Login --runner vitest bunx guren console bunx guren dev 7 -
Guren - Controller import type { ControllerInertiaProps } from '@guren/server'
import Controller from './Controller.js' import { Post } from '@/Models/Post.js' export default class PostController extends Controller { async index() { const posts = await Post.all() return this.inertia('posts/Index', { posts }) } } 8 -
Guren - View import type PostController from "@/Http/Controllers/PostController" import type
{ ControllerInertiaProps } from "@guren/server" type PostsIndexPageProps = ControllerInertiaProps<PostController, 'index'> export default function Index({ posts }: PostsIndexPageProps) { return ( <div> <h1>Posts</h1> <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ) } 9 -
Guren - Routing import { Route } from '@guren/server' import
PostController from '@/Http/Controllers/PostController.js' Route.get('/', [PostController, 'index']) Route.group('/posts', () => { Route.get('/', [PostController, 'index']) Route.get('/new', [PostController, 'create']) Route.get('/:id', [PostController, 'show']) Route.get('/:id/edit', [PostController, 'edit']) Route.post('/', [PostController, 'store']) Route.put('/:id', [PostController, 'update']) Route.patch('/:id', [PostController, 'update']) }) 10 -
Bun のデプロイ先の選択肢 Vercel Railway Render AWS Lambda DigitalOcean Google Cloud
Run → デプロイが簡単なものは料金高く、柔軟性(コンテナ)があるものは手順が複雑 参考: https://bun.com/docs/guides/deployment 12 -
Cloudflare は? Cloudflare Workers 単体: Bun を正式サポートしていない Cloudflare Containers: Workers
+ コンテナ実行環境 ← New! 2025 年6 月 パブリックベータリリース Region:Earth - ワンコマンドで世界320 都市以上にデプロイ 13 -
Cloudflare Containers とは Cloudflare Workers を拡張する、グローバルなコンテナ実行プラットフォーム グローバル展開 320 以上の都市で自動的にコンテナをプロ ビジョニング。リージョン設定は不要
シンプルなワークフロー wrangler deploy コマンド一つでデプロ イ完了。Docker と完全互換 14 -
Workers vs Containers 特徴 Cloudflare Workers Cloudflare Containers 用途 軽量なサーバーレス処理
重い計算処理、複雑なアプリ 対応言語 JavaScript/TypeScript 、Rust 任意の言語・ランタイム リソース 制限あり(128 MB メモリ) 最大12GB メモリ、4 vCPU 起動速度 超高速 やや遅い(コールドスタートあり) ファイルシステム 制限あり 完全なLinux 環境 15 -
Cloudflare Containers の主な特徴 Workers との深い統合: Workers をAPI Gateway 、Service Mesh
、Orchestrator として 活用。ルーティング、認証、キャッシュ制御が可能 オンデマンド起動: Durable Objects ベースのアーキテクチャ。コンテナインスタンス をコードで制御 プログラマブル: カスタムスケジューリング、スケーリング、ヘルスチェックを JavaScript で記述 scale to zero 課金: コンテナが動いていない時は課金なし。使った分だけ支払い Docker との互換性: 既存のDocker イメージをそのまま使用可能 16 -
利用料金 リソース 単価(超過時) 含まれる無料枠(Workers Paid $5/ 月 プラン) vCPU $0.000020
/ vCPU- 秒 375 vCPU- 分/月 メモリ $0.0000025 / GiB- 秒 25 GiB- 時/月 ディスク $0.00000007 / GB- 秒 200 GB- 時/月 Cloudflare Developers - Containers Pricing 17 -
ネットワーク(Egress )料金 地域 含まれる無料枠 超過料金(1 GB あたり) 北米・ヨーロッパ 1 TB
/月 $0.025 オセアニア・韓国・台湾 500 GB /月 $0.05 その他地域(Everywhere Else ) 500 GB /月 $0.04 Cloudflare Developers - Containers Pricing 18 -
想定されるユースケース メディア処理 動画エンコーディング、画像変換など重め の処理 コードサンドボックス LLM が生成したコードの安全な実行環境 バッチ処理 CLI ツールやデータ処理ワークロード
バックエンドサービス 任意の言語で書かれた既存アプリを動かす 19 -
Containers におけるWorkers の役割 API Gateway コンテナへのリクエスト前に、ルーティング、認証、キャッシュ、レート制限を制御 Service Mesh コンテナ間のプライベート接続を作成し、プログラマブルなルーティング層を提供 Orchestrator
カスタムスケジューリング、スケーリング、ヘルスチェックロジックを実装 21 -
Durable Objects とは 「状態を持ったサーバーレス実行単位」 。 「計算(compute )+ストレージ(storage ) 」 。
Cloudflare Containers の基盤となる技術。 主な機能 強い整合性: 各Durable Object インスタンスはグローバルに一意のID を持つ 低レイテンシ: リクエスト元に近い場所で自動的に実行され、データアクセスが高速 永続化: SQLite ベースのストレージで、データはディスクに永続化 WebSocket サポート: 長時間接続を維持し、リアルタイム通信が可能 22 -
Workers 側実装例 import { Container, getContainer } from "@cloudflare/containers"; export
class MyContainer extends Container { defaultPort = 4000; sleepAfter = "10m"; // 10 分間リクエストがなければ停止 } export default { async fetch(request, env) { const { "session-id": sessionId } = await request.json(); // セッションID ごとに固有のコンテナインスタンスを取得 const containerInstance = getContainer(env.MY_CONTAINER, sessionId); // コンテナにリクエストを転送 return containerInstance.fetch(request); }, }; 24 -
設定ファイル (wrangler.jsonc) { "name": "container-starter", "main": "src/index.js", "compatibility_date": "2025-11-08", "containers":
[ { "class_name": "MyContainer", "image": "./Dockerfile", "max_instances": 5 } ], "durable_objects": { "bindings": [ { "class_name": "MyContainer", "name": "MY_CONTAINER" } ] }, "migrations": [ { "new_sqlite_classes": ["MyContainer"], "tag": "v1" } ] } 25 -
Cron Container の例 - Workers 側実装例 import { Container, getContainer
} from "@cloudflare/containers"; export class CronContainer extends Container { sleepAfter = "5m"; } export default { async fetch(): Promise<Response> { return new Response( "This Worker runs a cron job to execute a container on a schedule.", ); }, // scheduled is called when a cron trigger fires async scheduled( _controller: any, env: { CRON_CONTAINER: DurableObjectNamespace<CronContainer> }, ) { await getContainer(env.CRON_CONTAINER).startAndWaitForPorts({ startOptions: { envVars: { MESSAGE: "Start Time: " + new Date().toISOString(), }, }, }); 26 -
Cron Container の例 - 設定ファイル (wrangler.jsonc) { "name": "cron-container", "main":
"src/index.ts", "triggers": { "crons": [ "*/2 * * * *" // Run every 2 minutes ] }, "containers": [ { "class_name": "CronContainer", "image": "./Dockerfile" } ], "durable_objects": { "bindings": [ { "class_name": "CronContainer", "name": "CRON_CONTAINER" } ] }, "migrations": [ { "new_sqlite_classes": ["CronContainer"], "tag": "v1" } 27 -
デプロイ wrangler deploy Docker イメージのビルドとプッシュ、自動的に行われる npx wrangler containers push <image>:<tag>
コマンドで手動プッシュしたイメ ージも使用可能 現在はCloudflare Registry のみ対応(今後は他レジストリも対応予定) 28 -
使用したDockerfile (抜粋) Guren アプリを動かすためのDockerfile FROM oven/bun:1.3 WORKDIR /app COPY web
./web WORKDIR /app/web RUN bun install --frozen-lockfile RUN bun run build ENV NODE_ENV=production \ HOST=0.0.0.0 \ PORT=4000 EXPOSE 4000 CMD ["bun", "bin/serve.ts"] 30 -
まずは普通にDocker イメージをビルド・実行してみる docker build -t guren-web:latest . docker run --rm
-it -p 3000:4000 --name guren-web-app guren-web:latest 31 -
原因調査 フロントエンドはVite + React + TailwindCSS の構成 それぞれビルドされたCSS の差分を 確認
どうやらTailwindCSS の一部欠けてい る様子 Wrangler 側のビルド環境の違い? 35 -
原因調査 似たような現象を発見 同じApple M2(MacOS) docker build でも --platform linux/amd64 を付けると再現すると
のこと Containers は linux/amd64 イメージで 動く 言われてみれば当たり前だけど、エラ ーじゃなかったので気づくのが遅れ た... 36 -
再現してみる docker build --platform linux/amd64 -t guren-web:latest . --platform linux/amd64
を付けてビルドしたイメージで再度動かしてみる 38 -
原因調査 その2 次はTailwindCSS 関連のIssue を調査 どうやらDocker Desktop のRosetta を 有効にしたMac
で linux/amd64 イメ ージをビルドすると発生する模様 https://github.com/rails/tailwindcss-rails/issues/553 40 -
解決策 Docker Desktop の Use Rosetta for x86_64/amd64 emulation on
Apple Silicon のチ ェックを外す 41 -
まとめ Bun アプリのデプロイ先としてCloudflare Containers も選択肢の一つ Containers はWorkers とDurable Objects を活用した強力なコンテナ実行環境
Containers で動かすときは linux/amd64 イメージで動作することを意識する 43