Sign inSign up

awkto/r2-gui

By awkto

•Updated about 2 months ago

Web GUI for managing Cloudflare R2 buckets and objects

Buildkit cache
Image
0

488

awkto/r2-gui repository overview

⁠R2 GUI

A small web GUI for managing Cloudflare R2 buckets and the files inside them — browse, upload, download, share, rename and delete, with a password-protected login and a settings page for swapping the R2 token.

Sister app to cloudflare-dns⁠, azure-dns⁠, dodns-gui⁠ and kea-reservations⁠ — same Flask + static-JS shape, same auth model, same deploy pipeline.

⁠Features

  • Multiple buckets — lists every bucket in the account, pick one from the dropdown
  • Folder browsing — S3 key prefixes rendered as folders, with breadcrumb navigation
  • Upload — multi-file picker or drag-and-drop anywhere on the page, with a progress bar. Dropping a directory preserves its structure.
  • Download — direct download via a short-lived presigned URL (the bytes never proxy through the app)
  • Share links — generate a presigned URL with a chosen expiry (15 min to 7 days)
  • Preview — inline preview for images and text files
  • Rename / move — for single objects or whole folders (server-side copy, then delete)
  • Delete — single items, multi-select, or a whole folder recursively
  • Bucket lifecycle — create and delete buckets from the UI
  • Refresh button, live filter, folders/files toggle, dark mode
  • Password login — single admin password, no username; plus a bearer API token for scripts

⁠Requirements

An R2 API token. In the Cloudflare dashboard: R2 → API → Manage API tokens → Create API token, and pick a permission level:

Token permissionBrowse & manage filesList bucketsCreate/delete buckets
Admin Read & Writeyesyesyes
Admin Read onlyread onlyyesno
Object Read & Writeyesnono
Object Read onlyread onlynono

Admin Read & Write is recommended. With an object-scoped token, ListBuckets is denied by R2, so enter the bucket names manually under Settings → R2 Credentials → Advanced → Bucket list and the app will use those instead.

⁠Two ways to configure it

Cloudflare shows you different things depending on where you make the token, so the app accepts either:

  • API token — if all you got was a token value, paste it. R2 derives its S3 credentials from the token: the Access Key ID is the token's own id, and the Secret Access Key is the SHA-256 hash of the token value. The app looks up the id via Cloudflare's token-verify endpoint and computes the hash, so neither has to be entered.
  • Access keys — if Cloudflare showed you an Access Key ID and Secret Access Key (the R2 → Manage API tokens flow does), paste those directly.

The Account ID is auto-detected when the token is allowed to read the account list; tokens scoped to R2 only usually are not, so it has to be entered once. It is not a secret — it is on the R2 overview page and in the dashboard URL.

⁠Running

docker run -d --name r2-gui --restart unless-stopped \
  -p 127.0.0.1:5000:5000 \
  -v /opt/r2-gui/data:/app/data \
  --label com.centurylinklabs.watchtower.enable=true \
  awkto/r2-gui:latest

Then open the app, set an admin password on first load, and add the R2 credentials under Settings. Everything is stored in /app/data/.env, so mount that directory to keep the configuration across restarts and image updates.

Or with compose:

docker compose up -d
⁠Behind a reverse proxy

Uploads go through the app, so raise the body-size limit. For nginx:

client_max_body_size 5g;
proxy_request_buffering off;
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;

⁠Configuration

Credentials are normally set in the UI, but can be pre-seeded with environment variables (see .env.example):

VariablePurpose
R2_ACCOUNT_IDCloudflare account ID (builds the S3 endpoint)
R2_ACCESS_KEY_IDR2 access key ID
R2_SECRET_ACCESS_KEYR2 secret access key
R2_API_TOKENCloudflare API token, when the two values above were derived from it
R2_ENDPOINTOptional endpoint override for jurisdiction-restricted buckets
R2_BUCKETSOptional comma-separated bucket names for object-scoped tokens

SESSION_SECRET, API_TOKEN and ADMIN_PASSWORD_HASH are generated on first run and written to /app/data/.env — do not set them by hand.

⁠API

Every /api/* route accepts either the browser session cookie or an Authorization: Bearer <token> header, where the token comes from Settings → API Token. Interactive docs live at /apidocs/.

GET    /api/health
GET    /api/buckets
POST   /api/buckets                          {name, location?}
DELETE /api/buckets/<bucket>
GET    /api/buckets/<bucket>/objects?prefix=
POST   /api/buckets/<bucket>/objects         multipart: prefix, files[]
DELETE /api/buckets/<bucket>/objects         {keys[], prefixes[]}
POST   /api/buckets/<bucket>/folders         {prefix, name}
POST   /api/buckets/<bucket>/move            {source, destination, is_folder}
GET    /api/buckets/<bucket>/download?key=
POST   /api/buckets/<bucket>/presign         {key, expires}
GET    /api/buckets/<bucket>/preview?key=
GET    /api/config          POST /api/config          POST /api/config/test

Example:

curl -H "Authorization: Bearer $R2GUI_TOKEN" https://r2.example.com/api/buckets

⁠Development

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python app.py     # http://localhost:5000

⁠Releasing

Tag with semver; the GitHub Action builds and pushes awkto/r2-gui:<version> and :latest to Docker Hub, and watchtower picks up the new :latest on the deployed host within ~5 minutes.

git tag v1.0.1 && git push origin main v1.0.1

Tag summary

Content type

Image

Digest

sha256:501406814…

Size

72.6 MB

Last updated

about 2 months ago

docker pull awkto/r2-gui