How to Configure R2-Backed Read-Only Mounts in Cloudflare Computer (and What Happens When You Try to Write)
Configure an R2-backed read-only mount by passing R2Bucket(binding, options) to your Workspace's mounts configuration with mode: "read-only" (the default); any write attempt returns EROFS immediately without touching R2.
Cloudflare Computer lets you mount Cloudflare R2 buckets directly into your workspace's virtual filesystem. This guide explains how to configure R2-backed read-only mounts using the R2Bucket eager-mount provider, what happens during materialization, and exactly how write operations are blocked.
Setting Up an R2-Backed Read-Only Mount
The R2Bucket provider in packages/computer/src/mounts/providers/r2.ts implements the EagerMount interface defined in packages/computer/src/mounts/types.ts. It streams bucket contents into the workspace at startup.
Provider Signature and Options
Call R2Bucket(binding, options?) where:
binding— Your Worker'sR2Bucketbinding (e.g.,env.Bucket)options— OptionalR2BucketOptionsobject:
| Option | Type | Default | Description |
|---|---|---|---|
prefix |
string |
undefined |
Only mount objects under this prefix |
listLimit |
number |
undefined |
Pagination limit for list() operations |
concurrency |
number |
8 |
Max concurrent get() operations during materialization |
mode |
"read-only" | "read-write" |
"read-only" |
Mount access mode (read-write is not yet implemented) |
Basic Configuration Example
import { Workspace, R2Bucket } from "@cloudflare/computer";
const ws = new Workspace({
storage: ctx.storage,
mounts: {
"/workspace/r2": R2Bucket(env.Bucket, {
prefix: "static/",
listLimit: 500,
// mode: "read-only" // default, explicit for clarity
}),
},
});
How Materialization Works
When the workspace boots, the mount's materialize() method executes once. The implementation in packages/computer/src/mounts/providers/r2.ts performs:
- List operation — Calls
R2BucketBinding.list()with the optional prefix - Concurrent streaming — For each key, calls
get()and pipes theReadableStreamdirectly toMountWriteAPI.writeFile() - Bounded concurrency — Default limit of 8 parallel operations prevents overwhelming R2
After successful materialization, the mount is marked indexed=1 in the internal _vfs_mounts table. Subsequent boots skip re-indexing unless the underlying store is rebuilt.
Important: New objects added to the bucket after initial materialization are not automatically reflected. You must recreate the workspace over a fresh store to pick up changes.
Read-Only Enforcement and Write Behavior
With mode: "read-only", the Workspace.fs implementation rejects any write operation targeting paths under the mount point.
What Happens on Write Attempts
| Operation | Result | R2 Interaction |
|---|---|---|
writeFile() |
Throws EROFS |
None — fails before any R2 call |
unlink() / rm() |
Throws EROFS |
None |
mkdir() / rmdir() |
Throws EROFS |
None |
rename() |
Throws EROFS |
None |
The POSIX error EROFS (Error Read-Only File System) is returned immediately. No data reaches R2, and the bucket remains untouched.
Write Attempt Example
try {
await ws.fs.writeFile("/workspace/r2/new-file.txt", "content");
} catch (e) {
console.error(e.code); // "EROFS"
console.error(e.message); // "EROFS: read-only file system"
}
// Directory creation also blocked
try {
await ws.fs.mkdir("/workspace/r2/new-dir");
} catch (e) {
console.error(e.code); // "EROFS"
}
Future: Read-Write Mode
The source code contains a comment in packages/computer/src/mounts/providers/r2.ts indicating planned functionality:
// put / delete proxies join later when the write-back path lands
This suggests a future "read-write" mode that would forward writes back to the R2 bucket. Until implemented, all mounts remain strictly read-only regardless of mode setting.
Key Implementation Files
packages/computer/src/mounts/providers/r2.ts—R2Bucketprovider implementation with materialization logic and read-only mode enforcementpackages/computer/src/mounts/types.ts—EagerMountinterface and mount type definitionspackages/computer/src/mounts/providers/r2.test.ts— Test coverage for read-only behavior and pagination edge cases
Summary
- Use
R2Bucket(env.Bucket, options)to mount R2 buckets at workspace paths - Default
mode: "read-only"streams bucket contents at boot and blocks all writes - Materialization runs once per store; new bucket objects require workspace recreation
- Write attempts return
EROFSimmediately with no R2 API calls - Read-write mode is planned but not yet implemented according to source code comments
Frequently Asked Questions
Can I make an R2-backed mount writable?
Not currently. The mode option accepts "read-only" (default) or "read-write", but according to the source code in packages/computer/src/mounts/providers/r2.ts, the read-write implementation is pending: the comment // put / delete proxies join later when the write-back path lands indicates this feature is planned for a future release. Attempting to set mode: "read-write" today has no effect.
Why don't new R2 objects appear in my mounted workspace?
The R2Bucket provider runs materialize() once when the workspace first boots over a fresh store. It marks the mount indexed=1 in the internal _vfs_mounts table, and subsequent boots skip re-indexing. Objects added to R2 after this initial snapshot are not automatically synchronized. Recreate the workspace over a new store to refresh the mount.
What error code do write attempts return?
Write operations targeting read-only R2 mounts return POSIX error EROFS (Error Read-Only File System). This includes writeFile, unlink, mkdir, rmdir, and rename. The error throws immediately from the Workspace.fs layer before any R2 API call occurs.
How does materialization handle large buckets?
The R2Bucket provider uses bounded concurrency (default 8 concurrent get() operations) to stream objects without overwhelming R2 or the worker. You can tune this with the concurrency option. For very large buckets, use the prefix option to mount only relevant subsets, and consider listLimit to paginate listing operations during testing.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →