A local-only Cloudflare Worker that exposes the S3 REST API and translates it onto an R2 Workers binding. Purpose: locally, R2 is only reachable via bindings, but in production R2 also exposes an S3 API. This worker lets S3 clients — in particular tigrisfs FUSE mounts — talk to your local R2 data.
⚠️ Local development only. Auth signatures are not verified. Do not expose publicly.
-
Edit
r2s3.config.jsonc— list the S3 bucket names you want and a port: -
Start it:
pnpm install pnpm dev my-app-data,thumbnails # comma-separated buckets — no config file pnpm dev my-app-data --port 9100 # optional port override pnpm dev # falls back to ./r2s3.config.jsonc pnpm dev path/to/other.jsonc # or an explicit config file
The config file is optional — the comma-separated shorthand covers the common case. A file is useful when you want to pin
persistTo/portalongside buckets.This generates an ephemeral wrangler config (
.r2s3/wrangler.gen.jsonc) with one R2 binding per bucket and runswrangler dev. R2 bindings are opaque slots (B0,B1, …); the bucket-name → binding map is emitted as theR2S3_BUCKETSvar and resolved at runtime. You never hand-edit a wrangler file. -
Point an S3 client at
http://127.0.0.1:9000with path-style addressing and any credentials. Example tigrisfs mount:AWS_ACCESS_KEY_ID=local AWS_SECRET_ACCESS_KEY=local \ tigrisfs --endpoint http://127.0.0.1:9000 my-app-data /mnt/r2
See NOT_IMPLEMENTED.md for operations that are intentionally omitted or degraded. The implemented surface targets exactly what tigrisfs calls.
scripts/gen-wrangler.mjs— config → ephemeral wrangler generator (accepts a config path arg).scripts/dev.mjs— generate +wrangler dev(accepts a config path arg).scripts/e2e-mount.sh+test/e2e-mount/— real tigrisfs FUSE mount test (Docker; arch auto-selected).src/index.ts— Hono app + S3 request dispatcher (method + query markers).src/buckets.ts— request parsing (path/virtual-host) + bucket→binding resolution.src/handlers/*— per-operation handlers.src/{headers,xml,s3-error}.ts— S3↔R2 header mapping, XML, error responses.
{ "buckets": ["my-app-data", "thumbnails"], "port": 9000 }