https://artifacts.aegeanai.com, a Cloudflare R2 bucket (eaia-artifacts) served through a custom domain binding. Rerun .rrd recordings, manim mp4s, and demo previews all live there.
Canonical setup and automation
The bucket setup (custom domain, CORS, Cloudflare Access bypass, API tokens) and the production upload helper live in the auraison repo. Do not re-derive them here.- Serve Rerun recordings publicly with Cloudflare R2, an 8-step bucket setup
auraison/data-plane/lakehouse/artifacts.py, whoseupload_artifact()writes to archival MinIO, R2, and the DuckLake catalog in one call, with lineage trackedauraison/infra/terraform/stacks/cloudflare/main.tf, which declares the bucket, itsartifacts.aegeanai.comcustom domain, and the public-access bypass
Work vault as item R2-eaia-artifacts-token, and are mirrored into auraison/data-plane/.env as R2_ENDPOINT, R2_ACCESS_KEY, R2_SECRET_KEY, R2_BUCKET, and R2_PUBLIC_DOMAIN. Treat 1Password as the source of truth; the .env copy has gone stale before. For the S3 API the keys are derived rather than issued: ACCESS_KEY_ID is the Cloudflare token id from /accounts/<id>/tokens/verify, and SECRET_ACCESS_KEY is the sha256 of the token value. Without that derivation a valid token looks dead, returning AccessDenied on every call.
Cloudflare-aware Claude Code sessions get the cloudflare and wrangler skills, covering R2 buckets, Workers, KV, D1, and the wrangler r2 object put upload path:
One-off uploads of website media
For media that is purely a docs-site asset, with no lakehouse lineage needed, use wrangler directly:--remote flag is required to write to the real bucket. Without it, wrangler writes to a local simulator.
Key-naming convention
The public URL ishttps://artifacts.aegeanai.com/<key>. Keep the <type>/<topic>/<filename> shape:
Path segments are lowercase and kebab-case. Filenames may follow the generator’s convention, such as PascalCase manim Scene class names.
Embedding in MDX
Manim mp4
muted and playsInline are both required for mobile-browser auto-play. Drop them for narrated full-frame demos.
Rerun recording
app.rerun.io. That is the part worth understanding before changing any of this.
We embedded the hosted viewer at app.rerun.io/version/<x>/ until August 2026, when the VisDrone card started rendering an error. The failure moved as we chased it. First re_viewer_bg.wasm returned an S3 AccessDenied and the viewer reported instantiate(): expected magic word, which is WebAssembly rejecting an XML error document where it expected a module, not a graphics fault, so the WebGL and WebGPU links on the error page led nowhere. Bumping to a version that appeared to be served produced a plain access denial instead, and within the hour that version’s index.html was being refused too. Every asset under every version we tried was eventually denied. Whether Rerun deprovisioned the builds or began refusing embedded requests, we could not establish, and it does not change what to do: a public homepage cannot depend on it.
So we host the viewer ourselves. Rerun publishes it as @rerun-io/web-viewer on npm, versioned in lockstep with the SDK, and the contents drop straight into the bucket:
application/wasm or streaming instantiation refuses it, which reproduces the original error on our own infrastructure. index.js does an extensionless import("./re_viewer"), a bundler convention that a browser will not resolve, so the glue is uploaded twice, once under each name. And the viewer reads its wasm as ./re_viewer_bg.wasm relative to its own module URL, so all five objects have to sit in one directory.
Version still couples the viewer to the recording, which is why both carry 0.36 in their paths. A viewer reads its own minor version and the one before it, so a 0.36 viewer accepts 0.36 and 0.35 recordings. Older recordings are migrated rather than replaced, and the migrated copy keeps its version in the key so the pairing stays legible:
rerun/viewer/<version>/ directory beside the current one and repointing the embeds. Nothing needs deleting, and the old viewer keeps working for any recording still pinned to it.
Cache invalidation
Wrangler sets a long max-age on R2 objects by default, so treat artifacts as content-addressed: a re-rendered manim scene should land under a new key, either a version suffix or a new topic, rather than overwriting the old one. The Cloudflare edge holds stale copies of an overwritten key for hours to a day. If you must overwrite, purge the cache afterwards from the Cloudflare dashboard or withwrangler cache purge.

