S3 互換のオブジェクトストレージバケットを、ローカルのファイルシステムパスとしてマウントします。標準のファイル操作でオブジェクトストレージにアクセスできます。本番の Cloudflare R2 では、Worker の R2 バインディング名でマウントし、認証情報を Worker ランタイム内に置けます。
コンテナへ認証情報を渡さずに本番で R2 バケットをマウントするには、R2 バインディングを追加し、Worker のエントリポイントから ContainerProxy をエクスポートします。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "my-r2-bucket"
}
]
}[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-r2-bucket"import { ContainerProxy } from "@cloudflare/sandbox";
export { ContainerProxy };import { ContainerProxy } from "@cloudflare/sandbox";
export { ContainerProxy };endpoint を省略する場合、mountBucket() の第 1 引数は MY_BUCKET のような Worker の R2 バインディング名である必要があります。
次が必要なときに、S3 互換バケットをマウントします。
- 永続データ - サンドボックスを破棄してもデータが残ります
- 大きなデータセット - ダウンロードせずにデータを処理できます
- 共有ストレージ - 複数のサンドボックスが同じデータにアクセスできます
- 費用対効果の高い永続化 - サンドボックスを起動したままにするより安くなります
import { getSandbox } from "@cloudflare/sandbox";
const sandbox = getSandbox(env.Sandbox, "data-processor");
// Mount R2 bucket by Worker binding name
await sandbox.mountBucket("MY_BUCKET", "/data");
// Access bucket with standard filesystem operations
await sandbox.exec("ls", { args: ["/data"] });
await sandbox.writeFile("/data/results.json", JSON.stringify(results));
// Use from Python
await sandbox.exec("python", {
args: [
"-c",
`
import pandas as pd
df = pd.read_csv('/data/input.csv')
df.describe().to_csv('/data/summary.csv')
`,
],
});import { getSandbox } from '@cloudflare/sandbox';
const sandbox = getSandbox(env.Sandbox, 'data-processor');
// Mount R2 bucket by Worker binding name
await sandbox.mountBucket('MY_BUCKET', '/data');
// Access bucket with standard filesystem operations
await sandbox.exec('ls', { args: ['/data'] });
await sandbox.writeFile('/data/results.json', JSON.stringify(results));
// Use from Python
await sandbox.exec('python', { args: ['-c', `
import pandas as pd
df = pd.read_csv('/data/input.csv')
df.describe().to_csv('/data/summary.csv')
`] });この例の MY_BUCKET は wrangler.toml のバインディング名です。ダッシュボード上のバケット名と一致させる必要はありません。多くのプロジェクトでは同じ名前を使います。
R2 バインディングマウントに認証情報は不要です。リモートエンドポイントマウントは、Cloudflare R2 とそのほかの S3 互換プロバイダー向けに引き続き使えます。これらの流れでは、認証情報の自動検出または明示的な認証情報を使えます。
endpoint を含める場合、認証情報を Worker シークレットとして設定すると、SDK が自動で検出します。
npx wrangler secret put R2_ACCESS_KEY_ID
npx wrangler secret put R2_SECRET_ACCESS_KEY// Credentials automatically detected from environment for remote endpoint mounts
await sandbox.mountBucket("my-r2-bucket", "/data", {
endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
});// Credentials automatically detected from environment for remote endpoint mounts
await sandbox.mountBucket('my-r2-bucket', '/data', {
endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com'
});必要なときは、認証情報を直接渡します。
await sandbox.mountBucket("my-r2-bucket", "/data", {
endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
},
});await sandbox.mountBucket('my-r2-bucket', '/data', {
endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com',
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY
}
});明示的な認証情報でマウントすると、s3fs はそれらの認証情報をコンテナのディスク上のパスワードファイルへ書き込みます。侵害されたコンテナプロセスは認証情報を読み取り、持ち出せます。意図したバケット範囲の外のストレージへアクセスすることもできます。
credentialProxy: true を設定すると、認証情報をコンテナの外に置けます。本物の認証情報をコンテナへ渡す代わりに、Durable Object がネットワーク層ですべての送信 S3 リクエストを傍受し、本物の認証情報で再署名してから上流へ転送します。コンテナが持つのはダミーの認証情報だけで、プロキシの外では使えません。
これは S3 互換エンドポイント(R2 を含む)向けの AWS SigV4 ↗ 署名と、Google Cloud Storage 向けの HMAC 署名に対応します。エンドポイント指定のマウントでは、すべて credentialProxy: true を設定することを推奨します。後方互換のためデフォルトは false で、Sandbox SDK の将来のバージョンではデフォルトになります。
await sandbox.mountBucket("my-bucket", "/data", {
endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
provider: "r2",
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
},
credentialProxy: true,
});await sandbox.mountBucket('my-bucket', '/data', {
endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com',
provider: 'r2',
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY
},
credentialProxy: true
});prefix オプションで、バケット内の特定のサブディレクトリだけをマウントします。プレフィックス配下の内容だけがマウントポイントに見えます。
// Mount only the /uploads/images/ subdirectory
await sandbox.mountBucket("MY_BUCKET", "/images", {
prefix: "/uploads/images/",
});
// Files appear at mount point without the prefix
// Bound bucket: my-r2-bucket/uploads/images/photo.jpg
// Mounted path: /images/photo.jpg
await sandbox.exec("ls", { args: ["/images"] });
// Write to subdirectory
await sandbox.writeFile("/images/photo.jpg", imageData);
// Creates my-r2-bucket/uploads/images/photo.jpg
// Mount different prefixes to different paths
await sandbox.mountBucket("MY_BUCKET", "/training-data", {
prefix: "/ml/training/",
});
await sandbox.mountBucket("MY_BUCKET", "/test-data", {
prefix: "/ml/testing/",
});// Mount only the /uploads/images/ subdirectory
await sandbox.mountBucket('MY_BUCKET', '/images', {
prefix: '/uploads/images/'
});
// Files appear at mount point without the prefix
// Bound bucket: my-r2-bucket/uploads/images/photo.jpg
// Mounted path: /images/photo.jpg
await sandbox.exec('ls', { args: ['/images'] });
// Write to subdirectory
await sandbox.writeFile('/images/photo.jpg', imageData);
// Creates my-r2-bucket/uploads/images/photo.jpg
// Mount different prefixes to different paths
await sandbox.mountBucket('MY_BUCKET', '/training-data', {
prefix: '/ml/training/'
});
await sandbox.mountBucket('MY_BUCKET', '/test-data', {
prefix: '/ml/testing/'
});読み取り専用でバケットをマウントし、データを保護します。
await sandbox.mountBucket("MY_BUCKET", "/data", {
readOnly: true,
});
// Reads work
await sandbox.exec("cat", { args: ["/data/dataset.csv"] });
// Writes fail
await sandbox.writeFile("/data/new-file.txt", "data"); // Error: Read-only filesystemawait sandbox.mountBucket('MY_BUCKET', '/data', {
readOnly: true
});
// Reads work
await sandbox.exec('cat', { args: ['/data/dataset.csv'] });
// Writes fail
await sandbox.writeFile('/data/new-file.txt', 'data'); // Error: Read-only filesystemwrangler dev 中も、localBucket オプションを渡して R2 バケットをマウントできます。本番の R2 バインディングマウントとローカルの localBucket マウントは、どちらも明示的な認証情報を使いませんが、実行経路は異なります。本番は認証情報なしの送信傍受で対象パスにオーバーレイします。ローカル開発は R2 バインディングとの定期同期です。
await sandbox.mountBucket("MY_BUCKET", "/data", {
localBucket: true,
});
// Access files using standard operations
await sandbox.exec("ls", { args: ["/data"] });
await sandbox.writeFile("/data/results.json", JSON.stringify(results));await sandbox.mountBucket('MY_BUCKET', '/data', {
localBucket: true
});
// Access files using standard operations
await sandbox.exec('ls', { args: ['/data'] });
await sandbox.writeFile('/data/results.json', JSON.stringify(results));readOnly と prefix は、ローカルモードでも同じように動作します。
// Read-only local mount
await sandbox.mountBucket("MY_BUCKET", "/data", {
localBucket: true,
readOnly: true,
});
// Mount a subdirectory
await sandbox.mountBucket("MY_BUCKET", "/images", {
localBucket: true,
prefix: "/uploads/images/",
});// Read-only local mount
await sandbox.mountBucket('MY_BUCKET', '/data', {
localBucket: true,
readOnly: true
});
// Mount a subdirectory
await sandbox.mountBucket('MY_BUCKET', '/images', {
localBucket: true,
prefix: '/uploads/images/'
});ローカル開発では、ファイルは直接のファイルシステムマウントではなく、定期同期で R2 とコンテナのあいだを同期します。次の点に注意してください。
- 同期の時間差 - ファイルを書き込んでから、反対側に見えるまで短い遅延があります。たとえば R2 へファイルをアップロードした直後に、コンテナのマウントパスから読むと、まだ使えないことがあります。最近書き込んだデータを読む前に、同期が終わる短い待ち時間を見てください。
- 高頻度の書き込み - 同じファイルパスへの連続した書き込みは、完全に伝播するまで少し長くかかることがあります。同じファイルを R2 とコンテナの両方から同時に書かないことを推奨します。
- 双方向同期 - コンテナ側の変更は R2 へ、R2 側の変更はコンテナへ同期します。どちらも同じ定期同期モデルです。
// Mount for processing
await sandbox.mountBucket("MY_BUCKET", "/data");
// Do work
await sandbox.exec("python process_data.py");
// Clean up
await sandbox.unmountBucket("/data");// Mount for processing
await sandbox.mountBucket('MY_BUCKET', '/data');
// Do work
await sandbox.exec('python process_data.py');
// Clean up
await sandbox.unmountBucket('/data');この SDK は任意の S3 互換オブジェクトストレージに対応します。よく使うプロバイダーの例です。
await sandbox.mountBucket("my-s3-bucket", "/data", {
endpoint: "https://s3.us-west-2.amazonaws.com",
credentials: {
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
},
});await sandbox.mountBucket('my-s3-bucket', '/data', {
endpoint: 'https://s3.us-west-2.amazonaws.com',
credentials: {
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY
}
});await sandbox.mountBucket("my-gcs-bucket", "/data", {
endpoint: "https://storage.googleapis.com",
credentials: {
accessKeyId: env.GCS_ACCESS_KEY_ID,
secretAccessKey: env.GCS_SECRET_ACCESS_KEY,
},
});await sandbox.mountBucket('my-gcs-bucket', '/data', {
endpoint: 'https://storage.googleapis.com',
credentials: {
accessKeyId: env.GCS_ACCESS_KEY_ID,
secretAccessKey: env.GCS_SECRET_ACCESS_KEY
}
});Backblaze B2、MinIO、Wasabi などでは、標準のマウントパターンを使います。
await sandbox.mountBucket("my-bucket", "/data", {
endpoint: "https://s3.us-west-000.backblazeb2.com",
credentials: {
accessKeyId: env.ACCESS_KEY_ID,
secretAccessKey: env.SECRET_ACCESS_KEY,
},
});await sandbox.mountBucket('my-bucket', '/data', {
endpoint: 'https://s3.us-west-000.backblazeb2.com',
credentials: {
accessKeyId: env.ACCESS_KEY_ID,
secretAccessKey: env.SECRET_ACCESS_KEY
}
});プロバイダー固有の設定は、対応プロバイダーと推奨フラグについて s3fs-fuse wiki ↗ を参照してください。
エラー: R2 binding "MY_BUCKET" not found in Worker env
対処: Worker に r2_buckets バインディングがあり、mountBucket() がダッシュボード上のバケット名ではなくバインディング名を使っていることを確認します。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "my-r2-bucket"
}
]
}[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-r2-bucket"対処: Worker のエントリポイントが ContainerProxy をエクスポートしていることを確認します。古い Wrangler を使っている場合は、enable_ctx_exports 互換性フラグも必要なことがあります。
エラー: MissingCredentialsError: No credentials found
対処: このエラーは、endpoint を設定してリモートの S3 互換エンドポイントをマウントするときにだけ発生します。認証情報を Worker シークレットとして設定します。
npx wrangler secret put R2_ACCESS_KEY_ID
npx wrangler secret put R2_SECRET_ACCESS_KEYまたは
npx wrangler secret put AWS_ACCESS_KEY_ID
npx wrangler secret put AWS_SECRET_ACCESS_KEYエラー: S3FSMountError: mount failed
よくある原因:
- エンドポイント URL が正しくない
- 認証情報が無効
ContainerProxyのエクスポートがない。古い Wrangler ではenable_ctx_exportsもない- バケットが存在しない
- ネットワーク接続の問題
バインディングまたはエンドポイントの設定を確認します。
try {
await sandbox.mountBucket("MY_BUCKET", "/data");
} catch (error) {
console.error("Mount failed:", error.message);
// Check binding name, ContainerProxy export, or remote endpoint configuration
}try {
await sandbox.mountBucket('MY_BUCKET', '/data');
} catch (error) {
console.error('Mount failed:', error.message);
// Check binding name, ContainerProxy export, or remote endpoint configuration
}エラー: InvalidMountConfigError: Mount path already in use
対処: 先にアンマウントするか、別のパスを使います。
// Unmount existing
await sandbox.unmountBucket("/data");
// Or use different path
await sandbox.mountBucket("bucket2", "/storage", { endpoint: "..." });// Unmount existing
await sandbox.unmountBucket('/data');
// Or use different path
await sandbox.mountBucket('bucket2', '/storage', { endpoint: '...' });マウントしたバケット上のファイル操作は、ネットワーク遅延のためローカルのファイルシステムより遅くなります。
対処: よく使うファイルはローカルへコピーします。
// Copy to local filesystem
await sandbox.exec("cp", {
args: ["/data/large-dataset.csv", "/workspace/dataset.csv"],
});
// Work with local copy (faster)
await sandbox.exec("python", {
args: ["process.py", "/workspace/dataset.csv"],
});
// Save results back to bucket
await sandbox.exec("cp", {
args: ["/workspace/results.json", "/data/results/output.json"],
});// Copy to local filesystem
await sandbox.exec('cp', { args: ['/data/large-dataset.csv', '/workspace/dataset.csv'] });
// Work with local copy (faster)
await sandbox.exec('python', { args: ['process.py', '/workspace/dataset.csv'] });
// Save results back to bucket
await sandbox.exec('cp', { args: ['/workspace/results.json', '/data/results/output.json'] });- 早めにマウントする - サンドボックス初期化時にバケットをマウントします
- マウントモードを選ぶ - Worker 管理の R2 アクセスなら R2 バインディングマウント、明示的な R2、S3、GCS、そのほかの S3 互換プロバイダーなら
endpointを使います - 認証情報を安全に扱う - 常に Worker シークレットを使い、ハードコードしないでください
- 可能なら読み取り専用にする - 読み取り専用マウントでデータを保護します
- 最も狭いパスをマウントする - プレフィックスで、サンドボックスが必要なデータだけを公開します
- マウントパス -
/data、/storage、/mnt/*を優先します。/workspace配下へマウントする場合は、本番でそのパスにオーバーレイされることを考慮します - エラーを扱う - マウント操作は
try...catchで囲みます - アクセスを最適化する - よく使うファイルはローカルへコピーします
- 永続ストレージのチュートリアル - R2 の完全な例
- バックアップと復元 -
/workspaceなどのプロジェクトディレクトリを永続化する - Storage API リファレンス - メソッドの完全なドキュメント
- 環境変数 - リモートエンドポイントマウントの認証情報設定
- Wrangler の設定 - R2 バインディングと互換性フラグの設定
- R2 ドキュメント - Cloudflare R2 について
- 送信トラフィック -
ContainerProxyと送信傍受の仕組み