Cloudflare Containersがベータリリース!コンテナがサーバーレスになる未来 【使い方から注意点、そして今後の展望まで】

はじめに
これは以前から2025年6月にベータ版がリリースされると告知されていた通りです。
◆Cloudflare Blog:https://blog.cloudflare.com/containers-are-available-in-public-beta-for-simple-global-and-programmable/
◆Cloudflare Doc:https://developers.cloudflare.com/containers/
◆Architecture:https://developers.cloudflare.com/containers/architecture/
Cloudflare画面内の下記場所にて「コンテナ」が増えてることが確認できました。

これまでCloudflareでコンテナを動かすことは出来ませんでしたが、今後はコンテナアプリケーションも利用可能になります。
もちろんCloudflare環境で動作するため、サーバーレスでサーバー管理も不要です。
そこで、Cloudflare Containersの始め方について検証してみました。
動作検証
事前準備
dockerのインストール
コンテナをローカルでビルドし、イメージをPUSHするため、ローカルPCでDockerが使用できる必要があります。
wranglerコマンドの準備
Wranglerコマンドが既にインストールされている環境では、デプロイ時にエラーが発生するため、Wranglerコマンドのアップデートが必要です。このコラム執筆時点では、Wranglerコマンドのバージョン4.23.0で検証しています。
また、アップデート後には `npx wrangler logout` を実行して再ログインしないとデプロイに失敗する可能性があるため、こちらも合わせて実施してください。
Getting Started
公式ドキュメントにあるGetting Startedを試しておくと理解が深まるのでおすすめです。
◆Cloudflare Doc:https://developers.cloudflare.com/containers/get-started/
◆サンプル:https://developers.cloudflare.com/containers/examples/
cronコンテナ
まず簡単なプログラムをコンテナで用意し、cron triggerを使用して動かしてみたいと思います。
構成と動き
① WorkersでCron Triggerを使用し、2分ごとに発火させます。
② 起動したらSecrets StoreからSlackのWebhook URLを取得します。
③ Webhook URLと変数をコンテナに渡し、実行します。
④ コンテナがAPIからデータを取得します。
⑤ 取得した内容をslackに通知します。

Slackのwebhook urlを登録
Cloudflare Secrets Storeに通知先のURL(SlackのWebhook URL)を登録します。
(※SlackのWebhook URLは、ご自身の環境のURLを入力してください)
$ npx wrangler secrets-store secret create ストアID --name SLACK_WEBHOOK_URL --scopes workers --remote
作成されたことを確認します。
$ npx wrangler secrets-store secret list <span>ストアID</span> --remote<br>...<br>┌───────────────────┬──────────────────────────────────┬─────────┬─────────┬─────────┬───────────────────┬───────────────────┐<br>│ Name │ ID │ Comment │ Scopes │ Status │ Created │ Modified │<br>├───────────────────┼──────────────────────────────────┼─────────┼─────────┼─────────┼───────────────────┼───────────────────┤<br>│ SLACK_WEBHOOK_URL │ xxxxx │ │ workers │ active │ 2025/7/7 14:18:25 │ 2025/7/7 14:18:26 │<br>└───────────────────┴──────────────────────────────────┴─────────┴─────────┴─────────┴───────────────────┴───────────────────┘
テンプレートからWorkers作成
作成するディレクトリ名を入力するよう求められるので、「./test-cron-container」とします。
$ npm create cloudflare@latest -- --template=cloudflare/templates/containers-template
...
In which directory do you want to create your application?
│ dir ./test-cron-containers
...
プログラムコード編集
Rustでコンテナを作成するため、一度 `container_src/` ディレクトリを削除してから `cargo new` で作成します。
$ rm -rf container_src/
$ cargo new container_src
$ vi container_src/Cargo.toml
`Cargo.toml` を以下のように編集します。
[package]
name = "container_src"
↓
[package]
name = "test-cron-containers"
...
[dependencies]
↓
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
serde_json = "1.0"
`container_src/src/main.rs` を以下のように編集します。
use reqwest::Client;
use serde_json::Value;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = Client::new();
// Cloudflare API から IP リストを取得
let cf_url = "https://api.cloudflare.com/client/v4/ips";
let resp = client
.get(cf_url)
.send()
.await?
.error_for_status()?
.json::<Value>()
.await?;
// Slack webhook 用ペイロードを作成
let text = format!(
"*Cloudflare IPs*
• IPv4: `{}`
• IPv6: `{}`",
resp["result"]["ipv4_cidrs"]
.as_array()
.unwrap_or(&vec![])
.iter()
.map(|v| v.as_str().unwrap_or(""))
.collect::()
.join(", "),
resp["result"]["ipv6_cidrs"]
.as_array()
.unwrap_or(&vec![])
.iter()
.map(|v| v.as_str().unwrap_or(""))
.collect::()
.join(", "),
);
let payload = serde_json::json!({ "text": text });
// Slack Incoming Webhook に POST
let webhook_url = std::env::var("SLACK_WEBHOOK_URL")
.expect("SLACK_WEBHOOK_URL 環境変数を設定してください");
client
.post(&webhook_url)
.json(&payload)
.send()
.await?
.error_for_status()?;
println!("Slack への通知が完了しました。");
Ok(())
}
`Dockerfile` を以下のように編集します。
ARG RUST_VERSION=1.88.0
ARG ALPINE_VERSION=3.22
ARG APP_NAME=test-cron-containers
FROM rust:${RUST_VERSION}-alpine${ALPINE_VERSION} AS build
ARG APP_NAME
WORKDIR /app
RUN apk add --no-cache
musl-dev
pkgconfig
openssl-dev
openssl-libs-static
RUN --mount=type=bind,source=container_src/src,target=src
--mount=type=bind,source=container_src/Cargo.toml,target=Cargo.toml
--mount=type=bind,source=container_src/Cargo.lock,target=Cargo.lock
--mount=type=cache,target=/app/target/
cargo build --locked --release &&
cp ./target/release/$APP_NAME /bin/server
FROM alpine:${ALPINE_VERSION} AS final
ARG UID=10001
RUN adduser
--disabled-password
--gecos ""
--home "/nonexistent"
--shell "/sbin/nologin"
--no-create-home
--uid "${UID}"
appuser
USER appuser
COPY --from=build /bin/server /bin/
CMD ["/bin/server"]
`src/index.ts` を以下のように編集します。
import { Container, getContainer } from "@cloudflare/containers";
export interface Env {
CRON_CONTAINER: DurableObjectNamespace<CronContainer>;
SLACK_WEBHOOK_URL: SecretsStoreSecret;
}
export class CronContainer extends Container {
manualStart = true;
sleepAfter = "10s";
override onStart() {
console.log("Starting container");
}
override onStop() {
console.log("Stopped container");
}
override onError(error: string) {
console.log("Container error:", error);
}
}
export default {
async scheduled(_controller: any, env: Env) {
const webhookUrl = await env.SLACK_WEBHOOK_URL.get();
if (!webhookUrl) {
console.error(
"Error: webhookUrl is null, undefined, or empty."
);
return;
}
let container = getContainer(env.CRON_CONTAINER);
await container.start({
envVars: {
SLACK_WEBHOOK_URL: webhookUrl,
MESSAGE: "Hello Cloudflare Containers",
},
});
},
};override onStart()、override onStop()、override onError(error: string)を使用すると、コンテナのstart時、stop時、error時にコードを実行できます。
◆Cloudflare Doc:https://developers.cloudflare.com/containers/examples/status-hooks/
`wrangler.jsonc`を以下のように編集します。
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "test-cron-containers",
"main": "src/index.ts",
"compatibility_date": "2025-05-23",
"compatibility_flags": ["nodejs_compat"],
"triggers": {
"crons": ["*/2 * * * *"]
},
"secrets_store_secrets": [
{
"binding": "SLACK_WEBHOOK_URL",
"store_id": "xxxxxxxxxxxx",
"secret_name": "SLACK_WEBHOOK_URL"
}
],
"observability": {
"enabled": true
},
"containers": [
{
"class_name": "CronContainer",
"image": "./Dockerfile",
"max_instances": 1,
"name": "test-cron-containers"
}
],
"durable_objects": {
"bindings": [
{
"class_name": "CronContainer",
"name": "CRON_CONTAINER"
}
]
},
"migrations": [
{
"new_sqlite_classes": ["CronContainer"],
"tag": "v1"
}
]
}deploy
準備ができましたのでdeployします。
$ npx wrangler deploy
Cloudflareの管理画面でWorkersが作成されたことを確認します。

コンテナも作成されたことを確認します。

コマンドからコンテナで使用しているイメージを確認することもできます。
$ npx wrangler containers images list
┌ Listing
REPOSITORY TAG
hello-containers 403a196f
コンテナの情報も確認できます。Kubernetesで `describe` コマンドで確認するようなイメージです。
$ npx wrangler containers list
● hello-containers (2025-07-06T09:49:24.750000128Z)
│ id: xxx-xxx-xxx-xxx-xxx
│ created_at: 2025-07-06T09:49:24.750000128Z
│ account_id: xxxxxxxxx
│ name: hello-containers
│ version: 1
│ scheduling_policy: default
│ instances: 7
│ max_instances: 10
│ configuration:
│ image: registry.cloudflare.com/xxxxxxxxx/hello-containers:5cbd2070
│ vcpu: 0.0625
│ memory: 256MB
│ memory_mib: 256
│ disk:
│ size: 2GB
│ size_mb: 2000
│ network:
│ assign_ipv6: none
│ assign_ipv4: none
│ mode: private
│ command:
│ entrypoint:
│ runtime: firecracker
│ observability:
│ logs:
│ enabled: true
│ logging:
│ enabled: true
│ constraints:
│ tier: 1
│ durable_objects:
│ namespace_id: xxx
│ health:
│ instances:
│ healthy: 7
│ failed: 0
│ scheduling: 0
╰ starting: 0
Workersをcronトリガーで動かしているため、2分ごとに以下の内容がSlackに通知されれば成功です。

Cloudflareでコンテナを動かすことが出来ました。
apiコンテナ
次に、簡単なAPIをコンテナで用意してみます。
構成と動き
① リクエストを送信します。
② Workersがリクエストを受け取ります(エンドポイントは2つ)。
③ コンテナがエンドポイントに応じてJSONデータを返します。

テンプレートからWorkers作成
作成するディレクトリ名の入力を求められますので、「./test-api-container」とします。
$ npm create cloudflare@latest -- --template=cloudflare/templates/containers-template
...
In which directory do you want to create your application?
│ dir ./test-api-containers
...
プログラムコード編集
$ rm -rf container_src/
$ cargo new container_src
$ vi container_src/Cargo.toml
`Cargo.toml` を以下のように編集します。
[package]
name = "container_src"
↓
[package]
name = "test-api-containers"
...
[dependencies]
↓
[dependencies]
actix-web = "4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
`container_src/src/main.rs` を以下のように編集します。
use actix_web::{web, App, HttpResponse, HttpServer};
async fn hello() -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({ "message": "hello" }))
}
async fn goodbye() -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({ "message": "goodbye" }))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/api/v1/hello", web::get().to(hello))
.route("/api/v1/goodbye", web::get().to(goodbye))
})
.bind(("0.0.0.0", 8080))?
.run()
.await
}`Dockerfile` を編集します。
ARG RUST_VERSION=1.88.0
ARG ALPINE_VERSION=3.22
ARG APP_NAME=test-cron-containers
FROM rust:${RUST_VERSION}-alpine${ALPINE_VERSION} AS build
ARG APP_NAME
WORKDIR /app
RUN apk add --no-cache
musl-dev
pkgconfig
RUN --mount=type=bind,source=container_src/src,target=src
--mount=type=bind,source=container_src/Cargo.toml,target=Cargo.toml
--mount=type=bind,source=container_src/Cargo.lock,target=Cargo.lock
--mount=type=cache,target=/app/target/
cargo build --locked --release &&
cp ./target/release/$APP_NAME /bin/server
FROM alpine:${ALPINE_VERSION} AS final
ARG UID=10001
RUN adduser
--disabled-password
--gecos ""
--home "/nonexistent"
--shell "/sbin/nologin"
--no-create-home
--uid "${UID}"
appuser
USER appuser
COPY --from=build /bin/server /bin/
EXPOSE 8080
CMD ["/bin/server"]
`src/index.ts` を編集します。
import { Container, getContainer, getRandom } from "@cloudflare/containers";
import { Hono } from "hono";
const INSTANCE_COUNT = 3;
export class MyContainer extends Container {
defaultPort = 8080;
sleepAfter = "10m";
override onStart() {
console.log("Container successfully started");
}
override onStop() {
console.log("Container successfully shut down");
}
override onError(error: unknown) {
console.log("Container error:", error);
}
}
const app = new Hono<{
Bindings: { MY_CONTAINER: DurableObjectNamespace<MyContainer> };
}>();
app.get("/", (c) => {
return c.text(
"Available endpoints:
" + "GET /api/v1/hello
" + "GET /api/v1/goodbye
"
);
});
app.get("/api/v1/*", async (c) => {
const containerInstance = await getRandom(c.env.MY_CONTAINER, INSTANCE_COUNT);
return await containerInstance.fetch(c.req.raw);
});
export default app;getRandom()関数を使用して、ランダムにインスタンスにアクセスがいくようにしています。
◆Cloudflare Doc:https://developers.cloudflare.com/containers/scaling-and-routing/
ただ、現在だとインスタンスの数を固定値で指定しなければいけないのでオートスケールには対応してないようです。今後のアップデートで対応されるようです。
`wrangler.jsonc` を編集します。
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"max_instances": 10,
"name": "hello-containers"
}
],
↓
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"max_instances": 10,
"name": "test-api-containers",
"instance_type": "standard"
}
],
今回はinstance_typeを指定して、スペックを変更してみました。
スペック情報は下記ドキュメントに記載されていますので参考にしてください。
◆Cloudflare Doc:https://developers.cloudflare.com/containers/platform-details/#instance-types
#### deploy
準備ができましたのでdeployします。
$ npx wrangler deploy
確認方法は1つ目のcron triggerのものを参考にしてみてください。
実際にアクセスして確認してみます。
FQDNはWorkersのworkers.devのFQDNを利用して確認していきます。
$ curl http://test-api-containers.xxx.workers.dev/
Available endpoints:
GET /api/v1/hello
GET /api/v1/goodbye
$ curl http://test-api-containers.xxx.workers.dev/api/v1/hello
{"message":"hello"}
$ curl http://test-api-containers.xxx.workers.dev/api/v1/goodbye
{"message":"goodbye"}
「/」はWorkers自体で返してます。
それ以外の「/api/v1/hello」、「/api/v1/goodbye」はコンテナから返してます。
問題なく返答ができていることが確認できました。
ただ、コンテナへのアクセスに関して最初は返答が遅かったです。
原因としては下記ドキュメントに書いてあるとおり、最初はコンテナを起動させるために遅くなるようです。
◆Cloudflare Doc:https://developers.cloudflare.com/containers/get-started/#routing-to-containers
一定の間隔でアクセスがある場合は良いのですが、もしそうでない場合はsleepAfterを長い時間指定して対応しておいたほうが良さそうです。
今後のロードマップ
Cloudflare Containersはベータ版となっており、GAリリース前にいくつかの変更が予定されています。
◆Cloudflare Doc:https://developers.cloudflare.com/containers/beta-info/
自動スケーリングや、負荷分散、ダッシュボードの更新などが予定されています。
制限
ベータ版のため利用に制限がありますので、試される方は下記URLを参考にしてください。
◆Cloudflare Doc:https://developers.cloudflare.com/containers/platform-details/#limits
最後に
検証している中で少し気になったところとして、下記を挙げておきます。
●コンテナのロケーションの指定方法が不明だったため、海外のロケーションにデプロイされてしまった
●変更が反映されるまで時間がかかる
もっと書きたいところもあったのですが、あまりに長くなってしまいますので別の機会にご紹介できればと思っています。
Cloudflareのブログで下記のように書かれており、今後もっと使いやすくなりそうです。
開発者プラットフォームとのさらなる統合
様々なサービス向けに、ファーストパーティAPIを活用した開発者プラットフォームとの統合を継続していきます。R2バケットのマウント、Hyperdriveへのアクセス、KVへのアクセスなどを非常に簡単に行えるようにしたいと考えています。
◆Cloudflare Blog:https://blog.cloudflare.com/containers-are-available-in-public-beta-for-simple-global-and-programmable/
そしてCloudflareなら上記以外でも、
・パフォーマンスの向上
・サイトの信頼性の向上
・セキュリティの向上
が、一つのサービスで実現できますので非常におすすめです。
Cloudflareに、アクセリアの運用サポートをプラスしたCDNサービスを提供しています
移行支援によるスムーズな導入とともに、お客様の運用負担を最小限にとどめながら、WEBサイトのパフォーマンスとセキュリティを最大限に高めます。運用サポートはフルアウトソーシングからミニマムサポートまで、ご要望に合わせてご提供します。
Cloudflare(クラウドフレア)の導入や運用について、またそれ以外のことでもなにか気になることがございましたらお気軽にご相談下さい。
サービスにご興味をお持ちの方は
お気軽にお問い合わせください。
Webからお問い合わせ
お問い合わせお電話からお問い合わせ
平日09:30 〜 18:00
Free Service












