Cloudflare R2 Setup Brief

Reels Video Storage Infrastructure

From: Charlotte (COO)
To: Stitch (CTO)
Date: 2026-02-25
Priority: LOW

Table of Contents

  1. Overview & Business Case
  2. Cloudflare Account Setup
  3. Bucket Configuration
  4. API Credentials
  5. CORS Configuration
  6. Backend Integration Notes
  7. File Organization & Key Patterns
  8. Cost Projections
  9. Implementation Checklist

1. Overview & Business Case

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:

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

  1. Go to https://dash.cloudflare.com
  2. Sign up for free Cloudflare account (or log in if existing)
  3. Navigate to R2 in the left sidebar
  4. Click "Create bucket"
  5. Name: stylify-reels
  6. Region: Leave as Automatic (Cloudflare selects geographically closest)
  7. 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:

  1. In Cloudflare R2 dashboard, select stylify-reels bucket
  2. Go to SettingsLifecycle rules
  3. Click "Add lifecycle rule"
  4. Rule name: auto-delete-old-videos
  5. Prefix: (leave blank — applies to whole bucket)
  6. Delete objects: 7 days after upload
  7. 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

  1. In Cloudflare dashboard, go to My Profile (top right) → API Tokens
  2. Click "Create Token"
  3. Choose template: "Edit Cloudflare R2"
  4. In Permissions section:
    • Set scope to: Account (not Zone)
    • R2 Permissions: Check "Object Read & Write"
    • Buckets: Select "stylify-reels" (specific bucket, not all)
  5. Set TTL to Never expire (or your preference)
  6. 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:

R2_ACCOUNT_ID=your-cloudflare-account-id-here
R2_ACCESS_KEY_ID=your-access-key-id-here
R2_SECRET_ACCESS_KEY=your-secret-access-key-here
R2_BUCKET_NAME=stylify-reels
R2_ENDPOINT=https://{R2_ACCOUNT_ID}.r2.cloudflarestorage.com

Note: Replace {R2_ACCOUNT_ID} in the endpoint URL with the actual account ID from Cloudflare.

5. CORS Configuration

The frontend needs permission to upload videos directly to R2 via presigned URLs. Configure CORS rules in the bucket:

  1. In Cloudflare R2, select stylify-reels bucket
  2. Go to SettingsCORS rules
  3. Click "Add CORS rule"
  4. Configure as follows:
Allowed Origins:
https://stylify-ai.com
http://localhost:5173 (local dev)

Allowed Methods:
GET, PUT, HEAD

Allowed Headers:
Content-Type, Content-Length

Max Age (seconds):
3600

This allows the frontend to upload videos (PUT) and check their status (HEAD) without browser CORS errors.

6. Backend Integration Notes

SDK & Dependencies

R2 is S3-compatible, so we use the standard AWS SDK:

npm install @aws-sdk/client-s3

Initializing the S3 Client

import { S3Client } from "@aws-sdk/client-s3";

const s3Client = new S3Client({
  region: "auto",
  endpoint: process.env.R2_ENDPOINT,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
  },
});

Presigned URL Upload (24-hour expiry)

The frontend requests an upload URL from the backend, then uploads directly to R2:

import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

async function getUploadUrl(userId, filename) {
  const key = `reels/${userId}/${Date.now()}_${uuid()}.mp4`;

  const command = new PutObjectCommand({
    Bucket: process.env.R2_BUCKET_NAME,
    Key: key,
    ContentType: "video/mp4",
  });

  const presignedUrl = await getSignedUrl(s3Client, command, {
    expiresIn: 86400 // 24 hours
  });

  return { presignedUrl, key };
}

Public URL for Instagram Download

Critical: Instagram needs a publicly accessible URL to download the video during container creation. Two options:

Option A: Public Bucket + Custom Domain (Recommended)

Option B: Presigned GET URLs (More Secure)

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

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)