# How to Configure R2-Backed Read-Only Mounts in Cloudflare Computer (and What Happens When You Try to Write)

> Learn to configure R2-backed read-only mounts in Cloudflare Computer. Discover what happens during write attempts, returning EROFS without R2 interaction.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: how-to-guide
- Published: 2026-08-14

---

**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`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/mounts/providers/r2.ts) implements the `EagerMount` interface defined in [`packages/computer/src/mounts/types.ts`](https://github.com/cloudflare/computer/blob/main/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's `R2Bucket` binding (e.g., `env.Bucket`)
- **`options`** — Optional `R2BucketOptions` object:

| 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

```ts
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`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/mounts/providers/r2.ts) performs:

1. **List operation** — Calls `R2BucketBinding.list()` with the optional prefix
2. **Concurrent streaming** — For each key, calls `get()` and pipes the `ReadableStream` directly to `MountWriteAPI.writeFile()`
3. **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

```ts
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`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/mounts/providers/r2.ts) indicating planned functionality:

```ts
// 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`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/mounts/providers/r2.ts)** — `R2Bucket` provider implementation with materialization logic and read-only mode enforcement
- **[`packages/computer/src/mounts/types.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/mounts/types.ts)** — `EagerMount` interface and mount type definitions
- **[`packages/computer/src/mounts/providers/r2.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/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 `EROFS` immediately 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`](https://github.com/cloudflare/computer/blob/main/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.