Easily make your R2 bucket publicly-readable on your own domain
R2 has an
S3-compatible API
, but does not yet support the public-read ACL, although it is on their roadmap.
We made an open-source utility worker available to do this prior to official support. It's a single one-time denoflare push
command to deploy it to your own account and zone (via
Custom Domains for Workers
), no git commands or any other tools necessary.
This worker might still be useful for use cases where you want to run custom logic or auth over standard public buckets.
Features
This open-source worker makes a single R2 bucket available via public-read with the following features:
- Supports conditional requests, range requests, and objects stored with pre-existing content-encoding
- (optional) Input flag to enable directory listing as html, with a configurable page limit
- (optional) Input flag to enable routing similar to
Cloudflare Pages
,
index.html
served for directories, root level404.html
etc - (optional) Allow/deny ip lists
- (optional) Enable CORS access to all or specific request origins, and optionally further restrict CORS access to specific file types
Prerequisites
You'll need:
- A Cloudflare account, with R2 billing enabled (see the official Purchase R2 instructions).
- An R2 bucket with data you want to make publicly available.
- A desired target domain (or subdomain) on an active Cloudflare zone in your account.
- A Cloudflare custom API token with permissions to manage Workers Scripts and Custom Domains for Workers for your target zone.
Minimal permissions needed for Custom Domains for Workers
Note: you'll need "Read Stream" permissions as well for some reason
You can limit these permissions to the target zone(s) for this worker.
- A working installation of
Deno
and
denoflare
(see installation)
Deploy it to your own account
You can deploy our public-read example worker like any other worker with denoflare push
.
Let's say your your Cloudflare account id is f2601bf4d2d5ddcb17981afe4db16dd2
, your API token secret is ABCDEFGHIJKLMNOPQRSTUVWXYZ
, and your bucket name is my-bucket
.
You can make this bucket available (for reading) at my-bucket.my-domain.com
with the following command:
denoflare push https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/examples/r2-public-read-worker/worker.ts \
--name my-bucket-public-read \
--r2-bucket-binding bucket:my-bucket \
--text-binding flags:listDirectories \
--text-binding allowCorsOrigins:* \
--custom-domain my-bucket.my-domain.com \
--account-id f2601bf4d2d5ddcb17981afe4db16dd2 \
--api-token ABCDEFGHIJKLMNOPQRSTUVWXYZ
That's it! 🎉
Your bucket will be available at https://my-bucket.my-domain.com
Your worker will be listed under your account, named my-bucket-public-read
.
Configuration
The worker takes five environment variables
bucket
: (required) Your r2 bucket nameflags
: (optional) Comma-separated flags:listDirectories
: Display an html listing for directoriesemulatePages
: Cloudflare Pages-like routing for directories using index.htmldisallowRobots
: Serve a robots.txt that disallows all robots, regardless of bucket contents
denyIps
: (optional) Comma-separated ip addresses to deny (applied first)allowIps
: (optional) Comma-separated ip addresses to allow (applied second)directoryListingLimit
: (optional) Page limit (inlistDirectories
mode)- Currently defaults to max (1000) due to a known R2 listing bug
allowCorsOrigins
: (optional) Comma-separated request origins for which CORS is allowed. e.g.*
orhttps://origin1.com, https://origin2.com
allowCorsTypes
: (optional) Comma-separated file extensions (.mp4, .m3u8, .ts
) or content types (video/mp4, application/x-mpegurl, video/mp2t
) to further restrict CORS, provided the origin is also allowed
Example
As with any Denoflare script, you can specify the environment variable bindings to denofare push
using the command line, or in your .denoflare
config file.
The following are equivalent:
denoflare push https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/examples/r2-public-read-worker/worker.ts \
--name my-bucket-public-read \
--r2-bucket-binding bucket:my-bucket \
--text-binding flags:disallowRobots,emulatePages \
--text-binding allowIps:1.2.3.4 \
--text-binding directoryListingLimit:20 \
--text-binding allowCorsOrigins:* \
--custom-domain my-bucket.my-domain.com \
--account-id f2601bf4d2d5ddcb17981afe4db16dd2 \
--api-token ABCDEFGHIJKLMNOPQRSTUVWXYZ
or
denoflare push my-bucket-public-read
With the following ~/.denoflare.jsonc
{
// For auto-completion!
"$schema": "https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/common/config.schema.json",
// Named worker script configurations
"scripts": {
// worker name
"my-bucket-public-read": {
// path can also be a local file path if you've modified the worker locally
"path": "https://raw.githubusercontent.com/skymethod/denoflare/v0.6.0/examples/r2-public-read-worker/worker.ts",
"bindings": {
"bucket": { "bucketName": "my-bucket" },
"flags": { "value": "disallowRobots,emulatePages" },
"allowIps": { "value": "1.2.3.4" },
"directoryListingLimit": { "value": "20" },
"allowCorsOrigins": { "value": "*" },
},
"customDomains": [ "my-bucket.my-domain.com" ],
},
},
// Cloudflare account credentials
"profiles": {
"token-1": {
"accountId": "f2601bf4d2d5ddcb17981afe4db16dd2",
"apiToken": "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
}
}
}
See the denoflare push documentation for more info