Skip to content

ADR-008: Cloudflare R2 for File Storage

Status: Accepted
Date: 2026-02-01
Deciders: Abdisamed Mohamed
Related ADRs: ADR-002 (Edge-First Architecture), ADR-005 (Durable Objects Usage), ADR-007 (Database Layer)


Context

CepatEdge requires robust file storage for maintenance request attachments including:

  • Before/after photos of maintenance work
  • Documentation files (PDFs, documents)
  • User avatars and profile images
  • System-generated reports

Requirements:

  • Global accessibility with low latency
  • Cost-effective storage with generous free tier
  • Integration with edge computing infrastructure
  • Secure access with proper authentication
  • Automatic scaling and high availability

Constraints:

  • Must work seamlessly with Cloudflare Workers
  • Should minimize database storage overhead
  • Files must be accessible globally with low latency
  • Cost should remain predictable and low

Decision

Use Cloudflare R2 as the primary file storage solution for all CepatEdge file uploads and attachments.

Implementation Strategy

Storage Architecture

typescript
// R2 integration with Workers
import { PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

// R2 client configuration
const r2Client = new S3Client({
  region: 'auto',
  endpoint: `https://${env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: env.R2_ACCESS_KEY_ID,
    secretAccessKey: env.R2_SECRET_ACCESS_KEY,
  },
});

File Organization

/cepatedge/
├── maintenance/
│   ├── {requestId}/
│   │   ├── before-{timestamp}.jpg
│   │   ├── after-{timestamp}.jpg
│   │   └── documentation.pdf
├── avatars/
│   └── {userId}/
│       └── profile-{timestamp}.jpg
└── reports/
    └── {reportId}/
        └── generated-{timestamp}.pdf

Upload Process

typescript
// Secure file upload with validation
app.post('/api/uploads', async (c) => {
  const user = c.get('user');
  const formData = await c.req.formData();
  const file = formData.get('file') as File;

  // Validate file type and size
  if (!isValidFile(file)) {
    return c.json({ error: 'Invalid file' }, 400);
  }

  // Generate secure filename
  const filename = generateSecureFilename(file.name, user.id);

  // Upload to R2
  const uploadUrl = await generatePresignedUploadUrl(filename);

  return c.json({ uploadUrl, filename });
});

Consequences

Positive

  • Edge Integration: Native Cloudflare integration with Workers
  • Global CDN: Automatic global distribution via Cloudflare CDN
  • Cost Effective: 10GB free storage, pay-per-GB after
  • Security: Built-in access controls and signed URLs
  • Performance: Low latency file access worldwide
  • Scalability: Automatic scaling with usage

Negative

  • Learning Curve: S3-compatible API different from other providers
  • Cloudflare Lock-in: Tied to Cloudflare ecosystem
  • Free Tier Limits: 10GB initial storage limit
  • Migration Complexity: Vendor-specific implementation

Mitigation

  • Abstraction Layer: Create unified file storage interface
  • Migration Planning: Design for future storage provider changes
  • Usage Monitoring: Track storage usage and plan for scaling
  • Documentation: Comprehensive file handling documentation

Performance Characteristics

Upload Performance

  • Direct Upload: <5 seconds for typical files (<10MB)
  • Chunked Upload: Support for large files with resumable uploads
  • Parallel Uploads: Multiple file uploads simultaneously

Access Performance

  • Global CDN: <100ms access time worldwide
  • Caching: Automatic CDN caching for frequently accessed files
  • Signed URLs: Secure temporary access links

Scalability

  • Storage Limits: Virtually unlimited scaling
  • Bandwidth: Global CDN handles traffic spikes
  • Concurrent Access: Thousands of simultaneous file accesses

Cost Optimization

Free Tier Utilization

  • Storage: 10GB included free
  • Bandwidth: Generous free bandwidth allocation
  • Requests: Included in Workers free tier

Scaling Costs

Storage (per GB/month)     | $0.015
Bandwidth (per GB)         | $0.010 (first 10TB)
Class A Operations         | $4.50 per million
Class B Operations         | $0.36 per million

Optimization Strategies

  • Compression: Automatic image compression and optimization
  • CDN Caching: Maximize cache hit ratios
  • File Deduplication: Avoid storing duplicate files
  • Lifecycle Policies: Automatic cleanup of old files

Security Implementation

Access Control

typescript
// Signed URLs for secure access
const signedUrl = await getSignedUrl(
  r2Client,
  new GetObjectCommand({
    Bucket: 'cepatedge', // Workers binding: env.CEPATEDGE_STORAGE
    Key: filename,
  }),
  { expiresIn: 3600 } // 1 hour expiry
);

File Validation

  • Type Checking: MIME type validation
  • Size Limits: Configurable file size restrictions
  • Virus Scanning: Integration with security services
  • Content Moderation: AI-powered content analysis

Audit Logging

  • Upload Tracking: Log all file uploads with user context
  • Access Logging: Track file access patterns
  • Security Events: Monitor for suspicious activity

Integration Points

Database Integration

sql
-- File metadata in database
CREATE TABLE attachments (
  id TEXT PRIMARY KEY,
  filename TEXT NOT NULL,
  original_name TEXT NOT NULL,
  mime_type TEXT NOT NULL,
  size INTEGER NOT NULL,
  r2_key TEXT NOT NULL UNIQUE,
  uploaded_by TEXT NOT NULL,
  request_id TEXT,
  created_at TIMESTAMP DEFAULT NOW()
);

API Integration

  • Upload Endpoints: Secure file upload APIs
  • Download Links: Temporary signed URLs
  • Metadata APIs: File information and management
  • Bulk Operations: Batch file operations

Alternatives Considered

AWS S3

  • Pros: Industry standard, extensive ecosystem
  • Cons: Higher latency, additional Cloudflare complexity

Cloudflare Images

  • Pros: Optimized for images, automatic optimization
  • Cons: Image-only, additional cost layer

Database Storage

  • Pros: Simple, transactional
  • Cons: Performance issues, storage limits

Local File System

  • Pros: Simple development
  • Cons: Not scalable, no global access

Migration Strategy

Current Implementation

  • Files stored locally during development
  • Database references with file paths
  • Simple file serving via web server

Migration Steps

  1. R2 Setup: Configure R2 bucket and access keys
  2. Upload Migration: Update upload endpoints to use R2
  3. Download Migration: Generate signed URLs for file access
  4. Database Update: Migrate file metadata to R2 references
  5. CDN Configuration: Set up Cloudflare CDN for file delivery

Rollback Plan

  • Maintain local file storage as fallback
  • Database supports both local and R2 references
  • Gradual migration with feature flags

Monitoring & Observability

Usage Metrics

  • Storage Usage: Track total storage consumption
  • Bandwidth Usage: Monitor data transfer costs
  • Request Patterns: Analyze file access patterns
  • Performance Metrics: Upload/download speed tracking

Operational Monitoring

  • File Upload Success Rate: Track upload reliability
  • Access Latency: Monitor file access performance
  • Error Rates: Track upload/download failures
  • Security Events: Monitor for unauthorized access attempts

Future Considerations

Advanced Features

  • Image Optimization: Automatic resizing and format conversion
  • Video Processing: Video upload and streaming capabilities
  • Backup Strategy: Cross-region replication for disaster recovery
  • Integration APIs: Third-party service integrations

Scaling Considerations

  • Multi-Bucket Strategy: Separate buckets for different file types
  • CDN Optimization: Advanced caching and delivery strategies
  • Global Distribution: Multi-region deployment strategy

References

  • Cloudflare R2 Documentation
  • AWS S3 Compatibility Guide
  • File Upload Security Best Practices
  • CDN Performance Optimization