Skip to content

Media Storage Plan — Backblaze B2 + Cloudflare CDN + NAS

Created 2026-03-27
Tags infrastructuremediaplanbackblazecloudflare

Status: Approved in principle — ready for step-by-step execution Date: 2026-03-27 Decisions made: B2 (not R2), private bucket, media.baseworks.com subdomain, NAS as local workspace, Google Drive stays as Andrew’s handoff


Images and media files stored in the Obsidian vault git repo cause:

  1. Build failures — KB site sync copies markdown but not vault assets. Image references crash the Astro build (this is what has been breaking deploys repeatedly).
  2. Git bloat — 125 image files (138 MB) committed. .git/ is 205 MB. Slows clone/pull/push across all machines.
  3. No scalability — Video, high-res photos, and growing campaign assets will compound the problem.

Backblaze B2 stores all media in a dedicated bucket (baseworks-media). Cloudflare CDN sits in front via a CNAME (media.baseworks.com), caching files at edge POPs worldwide with zero egress cost (Bandwidth Alliance partnership). Synology NAS serves as local workspace for browsing, sorting, and processing photos before upload.

Key facts:

  • B2 stores its own independent copy. If the NAS goes offline, images are still served from B2/Cloudflare. No dependency on home internet.
  • Cloudflare caches at edge POPs worldwide. Images load fast everywhere, same as any website asset.
  • Bucket is private by default. Files are not publicly listable. Access is controlled via Cloudflare caching rules — specific paths made accessible when campaigns go live.
  • Cost at 100 GB: ~$0.54/month storage. Egress: $0 via Cloudflare.

https://media.baseworks.com/{category}/{campaign-or-topic}/{filename}

Examples:

media.baseworks.com/campaigns/2026-03-winter-study-group-recap/baseworks-patrick-oancia-instructing-group.webp
media.baseworks.com/campaigns/2026-03-winter-study-group-recap/baseworks-patrick-oancia-instructing-group.jpg
media.baseworks.com/blog-articles/proprioceptive-awareness/dcml-pathway-diagram.png
media.baseworks.com/brand/baseworks-logo-dark.svg
media.baseworks.com/study-groups/2026-winter-montreal/session-7-group-photo.webp

The URL mirrors the folder structure in B2 and on the NAS. If you know the folder and filename, you know the URL.


LocationRoleWhat’s stored
Google Drive (shared link)Andrew’s drop pointRaw photos from photographer. Temporary — pulled down and deleted after processing.
Synology NAS /volume1/baseworks/media/Local workspace + archiveOriginals (full-res JPEGs), processed versions (WebP + JPEG). Browseable via Finder/SMB.
Backblaze B2 baseworks-media bucketCloud storage + CDN originProcessed versions (WebP + JPEG). Served via Cloudflare. Durable, independent of NAS.
Obsidian vaultMedia manifests (markdown only)No binary files. Campaign assets notes with image URLs, descriptions, and metadata.
Andrew → Google Drive → Patrick's Mac / NAS
/compress-photos
(WebP 80-85% + JPEG 90%)
upload script → B2 bucket
Cloudflare CDN (media.baseworks.com)
URLs used in Obsidian, KB site,
newsletters, social, baseworks.com

Different destinations need different formats. Both are stored in B2 side by side:

DestinationFormatQualityNotes
baseworks.com / KB siteWebP80-85%Smallest, all modern browsers
Newsletter (email)JPEG85%Some email clients don’t support WebP
Instagram / FacebookJPEG90-95%Platforms re-compress, so start higher
LinkedIn articlesJPEG or WebP85-90%Accepts both
Archive / printOriginal JPEG100%Stays on NAS only
/volume1/baseworks/media/
├── campaigns/
│ ├── 2026-03-winter-study-group-recap/
│ │ ├── originals/ ← raw JPEGs from Andrew (archive only, not uploaded to B2)
│ │ ├── *.webp ← web-optimized (uploaded to B2)
│ │ └── *.jpg ← high-quality JPEG for email/social (uploaded to B2)
│ └── ...
├── blog-articles/
├── study-groups/
├── brand/ ← logos, icons, recurring assets
└── incoming/ ← unsorted drop zone

No binary image files in the vault. Instead, each campaign (or asset collection) gets a media manifest — a markdown file listing all images with URLs, descriptions, and metadata:

02-areas/communications/campaigns/2026-03-winter-study-group-campaign/
├── 2026-03-winter-study-group-campaign.md ← campaign note
├── 2026-03-winter-study-group-recap-social.md ← social copy
└── 2026-03-winter-study-group-recap-assets.md ← media manifest

When you open the media manifest in Obsidian, images render inline (loaded from media.baseworks.com). Example content:

# Winter 2026 Study Group Recap — Media Assets
NAS source: `/volume1/baseworks/media/campaigns/2026-03-winter-study-group-recap/`
CDN base: `https://media.baseworks.com/campaigns/2026-03-winter-study-group-recap/`
## Group Instruction
![Patrick instructing the full group, wide shot](https://media.baseworks.com/campaigns/2026-03-winter-study-group-recap/baseworks-patrick-oancia-instructing-group.webp)
![Asia demonstrating seated form](https://media.baseworks.com/campaigns/2026-03-winter-study-group-recap/baseworks-asia-shcherbakova-seated-form.webp)
## Individual Feedback
![Patrick giving individualized feedback](https://media.baseworks.com/campaigns/2026-03-winter-study-group-recap/baseworks-patrick-oancia-individualized-feedback.webp)

When writing campaign copy in other markdown files, insert images inline:

![Patrick instructing study group](https://media.baseworks.com/campaigns/2026-03-winter-study-group-recap/baseworks-patrick-oancia-instructing-group.webp)

You don’t need to memorize the syntax. You can drop a filename or URL anywhere in the text and ask Claude Code to format it correctly.


  1. Reads media manifests to see all available images with descriptions, dimensions, and intended use
  2. Fetches and views images via their URLs to make visual judgments (e.g., which photo works best as a blog header)
  3. Searches across manifests to find images by description, campaign, or use case
  4. Browses NAS via SSH to discover available originals not yet indexed
  5. Inserts correctly formatted image references into campaign copy, blog posts, newsletters

Step 1 — Create B2 bucket (Patrick, in Backblaze dashboard)

Section titled “Step 1 — Create B2 bucket (Patrick, in Backblaze dashboard)”
  1. Log into secure.backblaze.com
  2. B2 Cloud Storage → Buckets → Create a Bucket
  3. Settings:
    • Bucket name: baseworks-media
    • Files in bucket: Public (Cloudflare needs to fetch files; access control happens at Cloudflare layer)
    • Default encryption: enabled
    • Object Lock: disabled
  4. Note the bucket endpoint: s3.us-east-005.backblazeb2.com

Step 2 — Create application key (Patrick, in Backblaze dashboard)

Section titled “Step 2 — Create application key (Patrick, in Backblaze dashboard)”
  1. App Keys → Add a New Application Key
  2. Settings:
    • Name: Go ahead and generate those keys. Is there anything else in here I need to check off or fill out
    • Allow access to: baseworks-media only
    • Type: Read and Write
  3. Copy both keyID and applicationKey immediately (shown once only)
  4. Share with Claude Code for CLI configuration

Step 3 — Configure B2 CLI on Mac (Claude Code)

Section titled “Step 3 — Configure B2 CLI on Mac (Claude Code)”

Authorize the B2 CLI with the new key. Verify bucket access.

Step 4 — Set up Cloudflare CNAME (Patrick, in Cloudflare dashboard, guided by Claude Code)

Section titled “Step 4 — Set up Cloudflare CNAME (Patrick, in Cloudflare dashboard, guided by Claude Code)”
  1. Cloudflare → baseworks.com zone → DNS
  2. Add CNAME record:
    • Name: media
    • Target: f005.backblazeb2.com (B2 native download endpoint — NOT the S3 endpoint)
    • Proxy: ON (orange cloud — enables CDN caching + zero egress)
  3. Add a Transform Rule (URL Rewrite) to prepend /file/baseworks-media to the path (B2 native endpoint requires this prefix; Cloudflare hides it so URLs stay clean)
  4. Add a Modify Request Header rule (HTTP Request Late Transform) to set Accept: */* for media.baseworks.com — this prevents Cloudflare APO (WordPress optimization) from intercepting browser requests and returning 404s

Step 5 — Create folder structure (Claude Code)

Section titled “Step 5 — Create folder structure (Claude Code)”
  • B2: create initial folder layout (campaigns/, blog-articles/, study-groups/, brand/, incoming/)
  • NAS: create matching structure at /volume1/baseworks/media/

Step 6 — Create upload script (Claude Code)

Section titled “Step 6 — Create upload script (Claude Code)”

Script at scripts/upload-media.sh:

  • Takes a local folder path and a B2 destination path
  • Uploads all files
  • Prints resulting media.baseworks.com URLs

Upload a test image, verify URL works via media.baseworks.com, verify it renders in an Obsidian note, verify Claude Code can fetch and view it.

  1. Upload current 125 image files (138 MB) to B2 at matching paths
  2. Create media manifests for existing campaigns
  3. Update markdown files to use media.baseworks.com URLs
  4. Add image extensions to .gitignore (except site/ directory assets like logos)
  5. Remove binary image files from vault working tree
  6. (Optional, later) Clean git history with BFG to reclaim ~100 MB

Step 9 — Update workflows and documentation

Section titled “Step 9 — Update workflows and documentation”
  • Update /compress-photos skill: add optional B2 upload + JPEG output
  • Update /create-campaign skill: generate media manifests instead of asset folders
  • Update campaign creation guidelines
  • Update CLAUDE.md with media handling rules
  • Create /upload-media skill for the full workflow

Found during planning (2026-03-27):

LocationCountSizeNotes
02-areas/.../2026-03-winter-study-group-recap-assets/~50127 MBWebPs, JPEGs, PNGs (campaign photos + primer prints)
03-resources/visuals/3~2 MBMeta ads, primer visualizer, infographic
00-inbox/attachments/3~1 MBFacebook post screenshots
assets/ (vault root)52.3 MBProprioception article diagrams
Downloads/slides_exported/13~5 MBExported slide JPEGs
_static/2<1 MBLogo dark/light PNGs
site/src/assets/3<1 MBSite logos (keep in git — needed for build)
Scattered root1<1 MByogajaya WebP

ItemMonthly Cost
B2 storage (first 10 GB)Free
B2 storage (10-50 GB)~$0.24
B2 storage (50-100 GB)~$0.54
B2 egress via Cloudflare$0 (Bandwidth Alliance)
B2 API calls (Class B reads, 100K/month)~$0.04
NAS$0 (owned)
Cloudflare CDN$0 (free plan)
Total at 100 GB~$0.58/month

  • No more KB site build failures from missing images
  • No git bloat — binary files never enter the repo again
  • Drop-and-go workflow: NAS → upload script → predictable URLs
  • Claude Code can browse, view, and curate the media library
  • Same URL works in Obsidian, KB site, email, social, baseworks.com
  • Images served globally via Cloudflare CDN, independent of NAS uptime
  • Private by default — nothing exposed until intentionally published
  • Andrew keeps using Google Drive for handoff (no workflow change for him)