Systems Engineering
Separating Source and Public Assets with Cloudflare R2
How two narrowly scoped R2 buckets keep private Markdown backups separate from public, cacheable images and downloadable files.
- Published
- Reading time
- 2 minutes
- Last verified
Source files and public media look similar in a file browser, but they have opposite security and caching requirements. Combining them in one object bucket makes both policies harder to reason about.
xiaoke.dev uses two Cloudflare R2 buckets with distinct responsibilities.
The private source mirror
xiaoke-source-private stores the latest complete Markdown mirror under posts/. It has no public domain and no public development URL.
The release process uses a checksum-based sync:
rclone sync ./src/content/blog r2:xiaoke-source-private/posts \
--checksum \
--delete-after \
--max-delete 10
Three details make this safer than a blind upload:
- unchanged files are not transferred;
- deletions happen after successful copies;
- an unexpectedly large deletion set stops the release.
The bucket still does not replace Git. It has no release directories and keeps no historical snapshots.
The public asset bucket
xiaoke-assets contains images and downloadable files. It is served through assets.xiaoke.dev, a production custom domain.
Public objects need correct metadata at upload time:
Content-Typeso browsers interpret the file correctly;Cache-Controlappropriate to the URL’s mutability;- stable, readable paths that can survive a site redesign.
An object can use a long immutable cache only when its URL changes with its content. A path such as /blog/an-article/cover.webp is readable, but it is not immutable if a future edit replaces the bytes. A generated filename containing a content hash can safely use a one-year immutable cache.
Credentials follow the boundary
The publishing environment should not use a single unrestricted token. The source mirror needs private read/write/list access to one bucket. The media uploader needs access to the public asset bucket. Cloudflare Pages deployment is a third responsibility.
Separating credentials limits the damage from a leaked token and makes release failures easier to diagnose.
Verify the object, not just the upload
A successful API response proves that R2 accepted bytes. It does not prove that the public URL serves the intended object.
The release smoke test should fetch representative assets and verify:
- the HTTP status;
Content-Type;Cache-Control;- a plausible content length.
This closes the gap between storage configuration and the reader’s actual experience.