Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Deploying Full-Stack Bun Applications on Cloudf...

Avatar for daiki7nohe daiki7nohe
November 12, 2025

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でハマった話

Avatar for daiki7nohe

daiki7nohe

November 12, 2025
Tweet

More Decks by daiki7nohe

Other Decks in Technology

Transcript

  1. 自己紹介 名前: 浦田 大貴(Urata Daiki ) 所属: 株式会社Fusic 仕事: エンジニア

    / AI 活用推進 趣味: 個人開発 / OSS 推し: Durable Objects X: @daiki7nohe 2
  2. Guren - 特徴 すでにある優秀なライブラリを採用 Model: Drizzle ORM View: React +

    Inertia.js によるAPI レスなSPA Controller/Routing: Hono Frontend Build/Testing: Vite 0→1 を高速に実装できることを目指す Bun のフルスタック性を活かせる(ランタイム、パッケージマネージャ、テストラン ナー、SQL 、S3 など) 6
  3. 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
  4. 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
  5. 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
  6. 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
  7. Bun のデプロイ先の選択肢 Vercel Railway Render AWS Lambda DigitalOcean Google Cloud

    Run → デプロイが簡単なものは料金高く、柔軟性(コンテナ)があるものは手順が複雑 参考: https://bun.com/docs/guides/deployment 12
  8. Cloudflare は? Cloudflare Workers 単体: Bun を正式サポートしていない Cloudflare Containers: Workers

    + コンテナ実行環境 ← New! 2025 年6 月 パブリックベータリリース Region:Earth - ワンコマンドで世界320 都市以上にデプロイ 13
  9. Workers vs Containers 特徴 Cloudflare Workers Cloudflare Containers 用途 軽量なサーバーレス処理

    重い計算処理、複雑なアプリ 対応言語 JavaScript/TypeScript 、Rust 任意の言語・ランタイム リソース 制限あり(128 MB メモリ) 最大12GB メモリ、4 vCPU 起動速度 超高速 やや遅い(コールドスタートあり) ファイルシステム 制限あり 完全なLinux 環境 15
  10. Cloudflare Containers の主な特徴 Workers との深い統合: Workers をAPI Gateway 、Service Mesh

    、Orchestrator として 活用。ルーティング、認証、キャッシュ制御が可能 オンデマンド起動: Durable Objects ベースのアーキテクチャ。コンテナインスタンス をコードで制御 プログラマブル: カスタムスケジューリング、スケーリング、ヘルスチェックを JavaScript で記述 scale to zero 課金: コンテナが動いていない時は課金なし。使った分だけ支払い Docker との互換性: 既存のDocker イメージをそのまま使用可能 16
  11. 利用料金 リソース 単価(超過時) 含まれる無料枠(Workers Paid $5/ 月 プラン) vCPU $0.000020

    / vCPU- 秒 375 vCPU- 分/月 メモリ $0.0000025 / GiB- 秒 25 GiB- 時/月 ディスク $0.00000007 / GB- 秒 200 GB- 時/月 Cloudflare Developers - Containers Pricing 17
  12. ネットワーク(Egress )料金 地域 含まれる無料枠 超過料金(1 GB あたり) 北米・ヨーロッパ 1 TB

    /月 $0.025 オセアニア・韓国・台湾 500 GB /月 $0.05 その他地域(Everywhere Else ) 500 GB /月 $0.04 Cloudflare Developers - Containers Pricing 18
  13. Durable Objects とは 「状態を持ったサーバーレス実行単位」 。 「計算(compute )+ストレージ(storage ) 」 。

    Cloudflare Containers の基盤となる技術。 主な機能 強い整合性: 各Durable Object インスタンスはグローバルに一意のID を持つ 低レイテンシ: リクエスト元に近い場所で自動的に実行され、データアクセスが高速 永続化: SQLite ベースのストレージで、データはディスクに永続化 WebSocket サポート: 長時間接続を維持し、リアルタイム通信が可能 22
  14. 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
  15. 設定ファイル (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
  16. 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
  17. 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
  18. デプロイ wrangler deploy Docker イメージのビルドとプッシュ、自動的に行われる npx wrangler containers push <image>:<tag>

    コマンドで手動プッシュしたイメ ージも使用可能 現在はCloudflare Registry のみ対応(今後は他レジストリも対応予定) 28
  19. 使用した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
  20. 原因調査 フロントエンドはVite + React + TailwindCSS の構成 それぞれビルドされたCSS の差分を 確認

    どうやらTailwindCSS の一部欠けてい る様子 Wrangler 側のビルド環境の違い? 35
  21. 原因調査 似たような現象を発見 同じApple M2(MacOS) docker build でも --platform linux/amd64 を付けると再現すると

    のこと Containers は linux/amd64 イメージで 動く 言われてみれば当たり前だけど、エラ ーじゃなかったので気づくのが遅れ た... 36
  22. 再現してみる docker build --platform linux/amd64 -t guren-web:latest . --platform linux/amd64

    を付けてビルドしたイメージで再度動かしてみる 38
  23. 原因調査 その2 次はTailwindCSS 関連のIssue を調査 どうやらDocker Desktop のRosetta を 有効にしたMac

    で linux/amd64 イメ ージをビルドすると発生する模様 https://github.com/rails/tailwindcss-rails/issues/553 40