We've decided on Cloudflare R2 for Instagram Reels video storage. This brief provides Stitch with everything needed to set up the infrastructure when the Reels build is triggered (10+ active users posting images).
Why Cloudflare R2?
Provider
Storage
Egress
At 100 Users
Notes
Cloudflare R2
$0.015/GB
$0
~$1.80/mo
Zero egress is the killer feature; S3-compatible
AWS S3
$0.023/GB
$0.09/GB
~$12-15/mo
Standard tier; egress kills budget at scale
Supabase Storage
$5/100GB
Included
~$5-10/mo
Simpler API, but R2 is cheaper + we own the bucket
Key decision: R2's zero egress fee means Instagram can download videos for container creation without eating our storage budget. This is non-negotiable for a salon SaaS at scale.
Expected Usage
Videos are temporary (7-day retention). Assuming average Instagram Reel = ~40MB:
100 active users: ~100 Reels/day → ~120GB in storage → ~$1.80/month
500 active users: ~500 Reels/day → ~600GB in storage → ~$9/month
Timeline
Not urgent. This brief is ready now, but the Reels build is deferred until we hit 10+ active users posting images daily. The infrastructure can be set up in one session when that threshold is reached.
2. Cloudflare Account Setup
R2 Pricing (Free Tier)
Component
Free Tier
Paid
Storage
10 GB/month
$0.015/GB above 10GB
Class A Operations (PUT, POST, DELETE)
1M/month
$4.50/1M above limit
Class B Operations (GET, LIST)
10M/month
$0.36/10M above limit
Egress
$0 (forever)
Setup Steps for Jason
Go to https://dash.cloudflare.com
Sign up for free Cloudflare account (or log in if existing)
Navigate to R2 in the left sidebar
Click "Create bucket"
Name: stylify-reels
Region: Leave as Automatic (Cloudflare selects geographically closest)
Click "Create bucket"
At 100 users, we stay well under the free tier limits (Class A: ~3M ops/month, Class B: ~1M reads/month). This remains free for Stylify indefinitely.
3. Bucket Configuration
7-Day Lifecycle Rule
Videos in R2 are temporary — Instagram downloads them during container creation, then we don't need them. Set up automatic deletion:
In Cloudflare R2 dashboard, select stylify-reels bucket
Go to Settings → Lifecycle rules
Click "Add lifecycle rule"
Rule name: auto-delete-old-videos
Prefix: (leave blank — applies to whole bucket)
Delete objects: 7 days after upload
Click "Create"
Why 7 days? Instagram downloads the video immediately during container creation. We keep it for 7 days as a safety buffer in case of re-uploads or audit trails, then clean up automatically. This keeps storage minimal and costs predictable.
4. API Credentials
Creating an R2 API Token
In Cloudflare dashboard, go to My Profile (top right) → API Tokens
Click "Create Token"
Choose template: "Edit Cloudflare R2"
In Permissions section:
Set scope to: Account (not Zone)
R2 Permissions: Check "Object Read & Write"
Buckets: Select "stylify-reels" (specific bucket, not all)
Set TTL to Never expire (or your preference)
Click "Create Token"
This generates three values: Account ID, Access Key ID, and Secret Access Key. Copy all three immediately.
Environment Variables for Railway
Add these to the Stylify backend's Railway environment:
Critical: Instagram needs a publicly accessible URL to download the video during container creation. Two options:
Option A: Public Bucket + Custom Domain (Recommended)
Make the R2 bucket public in settings
Bind a custom domain: videos.stylify-ai.com → R2 bucket
Public video URL: https://videos.stylify-ai.com/reels/{user_id}/{timestamp}_{uuid}.mp4
Pro: Clean URLs, CDN-cached by default
Con: Bucket is public (anyone can list objects if not configured)
Option B: Presigned GET URLs (More Secure)
Keep bucket private
Generate presigned GET URLs with 48-hour expiry when storing video metadata
Pass the presigned URL to Instagram API
Pro: Bucket stays private, access is time-limited
Con: URLs are long and expire (Instagram won't cache if URL expires before container creation completes)
Recommendation: Use Option A (public bucket + custom domain) for reliability. Configure bucket ACL so only authenticated requests via the domain can access objects. This is standard for CDN-backed video storage.
7. File Organization & Key Patterns
Store videos with a consistent key structure to keep the bucket organized and enable easy cleanup:
reels/{user_id}/{timestamp}_{uuid}.mp4
Example
reels/user_12345/1708956342_a7b3c9d1.mp4
Benefits
User isolation: Each stylist's videos are in their own folder (useful for analytics or debugging)
Timestamp: Enables sorting and knowing when a video was uploaded
UUID: Prevents collisions if multiple videos upload simultaneously from the same user
Lifecycle cleanup: The 7-day rule automatically deletes old files across all users
8. Cost Projections
Based on typical salon engagement (1 Reel/user/day, ~40MB per video, 7-day retention):
Active Users
Reels/Day
Monthly Storage
Monthly Cost
Notes
10
10
~12 GB
Free
Under free tier limit
50
50
~60 GB
~$0.75
50 GB × $0.015
100
100
~120 GB
~$1.80
110 GB above free tier × $0.015
500
500
~600 GB
~$9.00
590 GB above free tier × $0.015
Bottom line: Reels storage will be negligible cost. At 500 users, we're spending less than $10/month on video storage — less than a single Slack enterprise plan seat.
9. Implementation Checklist
When we hit 10+ active users and trigger the Reels build, use this checklist to set up R2:
Cloudflare Setup (Jason)
Create Cloudflare account (or verify existing)
Create R2 bucket: stylify-reels
Set 7-day lifecycle rule on bucket
Create R2 API token (Object Read & Write on stylify-reels bucket)
Configure CORS rules (origins, methods, headers)
Set up custom domain: videos.stylify-ai.com (or use presigned URLs)
Backend Integration (Stitch)
Add environment variables to Railway (R2_ACCOUNT_ID, keys, bucket name, endpoint)
Install @aws-sdk/client-s3 in backend dependencies
Create services/videoStorage.js with functions:
getUploadUrl(userId, filename) → presigned PUT URL
getPublicUrl(key) → public or presigned GET URL for Instagram
deleteVideo(key) → manual delete if needed (lifecycle handles auto-cleanup)
Create API endpoint: POST /api/reels/upload-url (returns presigned URL to frontend)
Test presigned URL upload from frontend (PUT request with video data)
Verify Instagram can fetch video from public URL (test with Instagram Graph API)
Test lifecycle rule (upload test video, verify deletion after 7 days)
Frontend (Stitch or Frontend team)
Add video upload component to Reel creation flow
Request presigned URL from backend API
Upload video to presigned URL (PUT request)
Get public URL of uploaded video from backend
Pass URL to Instagram container creation API
Test end-to-end: upload video → Instagram publishes Reel
Verification & Monitoring
Check R2 bucket storage usage in Cloudflare dashboard (should match projections)
Monitor API operation counts (Class A and Class B) — should stay under free tier limits
Spot-check that old videos are being deleted (sample 7-day-old object, verify it's gone)
Alert: If storage exceeds $50/month, review usage (may indicate duplicate uploads or buggy code)