Flexible Object Storage: Supporting MinIO, Local, and R2 for Complete Deployment Freedom
One of the biggest decisions when building fitness software is where to store user files - progress photos, exercise videos, profile pictures, and other assets. Most apps lock you into their preferred cloud provider or don't support file uploads at all. We built something different: a flexible object storage system that works whether you're running on a laptop, self-hosting with MinIO, or scaling with Cloudflare R2.
The File Storage Dilemma
Here's the problem with most fitness apps and file storage: they either don't support it, or they force you into their specific cloud setup. Want to self-host? Too bad, the app only works with AWS S3. Prefer local storage for privacy? Not an option. Need to switch providers later? Time for a complete rewrite.
This approach creates artificial limitations. Your choice of where to store files shouldn't dictate how you can deploy your fitness platform. Whether you're running a personal instance on your home server or scaling to thousands of users, the storage layer should adapt to your needs, not the other way around.
We wanted to build something that gives you complete control over where your data lives while maintaining the same feature set regardless of your storage choice.
Our Approach: Provider-Agnostic Object Storage
We built a flexible object storage abstraction that adapts to your deployment needs while providing the same features regardless of which backend you choose.
1. Clean Provider Interface
All storage providers implement the same interface, ensuring consistent functionality:
interface IStorageProvider {
uploadObject(input: ProviderUploadInput): Promise<ProviderUploadOutput>;
getObjectStream(input: ProviderGetInput): Promise<ProviderGetOutput>;
deleteObject(input: ProviderDeleteInput): Promise<ProviderDeleteOutput>;
generatePresignedGetUrl(input: ProviderPresignedInput): Promise<ProviderPresignedOutput>;
generatePresignedUploadUrl(input: ProviderPresignedUploadInput): Promise<ProviderPresignedOutput>;
checkObjectExists(input: ProviderExistsInput): Promise<boolean>;
}
Whether you're using local filesystem, MinIO, or Cloudflare R2, the application code remains identical. Switch providers with just a configuration change.
2. Local Storage Provider
Perfect for development, personal instances, and privacy-focused deployments:
# Local storage configuration
OBJECT_STORAGE_PROVIDER=LOCAL
LOCAL_STORAGE_BASE_PATH=./data/object-storage
LOCAL_STORAGE_BASE_URL=https://your-domain.com/api/storage
LOCAL_STORAGE_SECRET=your-signing-secret-32-chars-minimum
LOCAL_STORAGE_ALLOWED_EXTENSIONS=jpg,jpeg,png,gif,webp,heic,heif,mp4,mov,avi
LOCAL_STORAGE_MAX_FILE_SIZE=10485760 # 10MB
The local provider creates organized directory structures and implements its own signed URL system using HMAC-SHA256 tokens - no external dependencies required.
3. MinIO Provider
Self-hosted S3-compatible storage with full control:
# MinIO configuration
OBJECT_STORAGE_PROVIDER=MINIO
MINIO_ENDPOINT=https://minio.your-domain.com
MINIO_PORT=9000
MINIO_ACCESS_KEY=your-minio-access-key
MINIO_SECRET_KEY=your-minio-secret-key
MINIO_USE_SSL=true
MINIO_USER_ASSETS_BUCKET=user-assets
MINIO_APP_ASSETS_BUCKET=app-assets
MINIO_REGION=us-east-1
MinIO gives you S3 compatibility while keeping everything on your infrastructure. Perfect for organizations that need cloud-like scalability without cloud dependencies.
4. Cloudflare R2 Provider
Scalable cloud storage without egress fees:
# Cloudflare R2 configuration
OBJECT_STORAGE_PROVIDER=R2
R2_ACCOUNT_ID=your-cloudflare-account-id
R2_ACCESS_KEY_ID=your-r2-access-key
R2_SECRET_ACCESS_KEY=your-r2-secret-key
R2_USER_ASSETS_BUCKET=openlift-user-assets
R2_APP_ASSETS_BUCKET=openlift-app-assets
R2_USER_ASSETS_DOMAIN=assets.your-domain.com
R2_APP_ASSETS_DOMAIN=static.your-domain.com
R2 provides the scalability and reliability of cloud storage while eliminating the typical egress costs that make other providers expensive at scale.
How Provider Selection Works
The system automatically detects your provider configuration and initializes the appropriate backend:
// Factory pattern selects provider based on environment
const provider = ObjectStorageProviderFactory.create(config);
// All providers support the same operations
const uploadResult = await provider.uploadObject({
bucketType: StorageBucketType.USER_ASSETS,
key: 'users/12345/profile-picture/photo.jpg',
stream: fileStream,
contentType: 'image/jpeg',
size: fileSize
});
Provider-Specific Features
Each provider implements features using their optimal approach:
Local Storage:
- Directory-based organization (
./data/object-storage/user-assets/users/...) - Custom signed URLs with HMAC tokens
- Direct filesystem operations for maximum performance
MinIO:
- S3-compatible bucket operations
- Native presigned URL generation
- Built-in redundancy and replication support
Cloudflare R2:
- Global CDN distribution
- Custom domain support for branded URLs
- Zero egress fees regardless of bandwidth usage
Security Across Providers
All providers implement consistent security measures:
- File validation with MIME type and extension checking
- Size limits configurable per asset type and deployment
- Access control through time-limited signed URLs
- Path sanitization to prevent traversal attacks
The security model remains identical whether files are stored locally or in the cloud.
Real-World Deployment Scenarios
Development & Testing
Start with local storage for development - zero configuration and instant feedback:
# Quick local setup
mkdir -p ./data/object-storage
export OBJECT_STORAGE_PROVIDER=LOCAL
export LOCAL_STORAGE_BASE_PATH=./data/object-storage
export LOCAL_STORAGE_SECRET=$(openssl rand -hex 32)
npm run dev
Personal Self-Hosting
Local storage works perfectly for personal instances. Your data stays completely private and you control retention policies:
# docker-compose.yml
services:
openlift:
environment:
- OBJECT_STORAGE_PROVIDER=LOCAL
- LOCAL_STORAGE_BASE_PATH=/app/data/storage
volumes:
- ./storage-data:/app/data/storage
Team & Organization Deployments
MinIO provides S3 compatibility with complete control over your infrastructure:
# docker-compose.yml with MinIO
services:
minio:
image: minio/minio
command: server /data --console-address ":9001"
volumes:
- minio-data:/data
environment:
- MINIO_ROOT_USER=your-access-key
- MINIO_ROOT_PASSWORD=your-secret-key
openlift:
environment:
- OBJECT_STORAGE_PROVIDER=MINIO
- MINIO_ENDPOINT=http://minio:9000
- MINIO_ACCESS_KEY=your-access-key
- MINIO_SECRET_KEY=your-secret-key
Scaling to Production
Cloudflare R2 handles the complexity of global distribution and eliminates egress fees:
# Production R2 configuration
OBJECT_STORAGE_PROVIDER=R2
R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY_ID=your-r2-key
R2_SECRET_ACCESS_KEY=your-r2-secret
R2_USER_ASSETS_BUCKET=prod-user-assets
R2_USER_ASSETS_DOMAIN=assets.yourapp.com
What This Storage Foundation Enables
Object storage infrastructure unlocks visual features across OpenLift:
- Profile Photos: User avatars and identity (available now)
- Progress Photo Galleries: Before/after comparisons with timeline views
- Exercise Demonstration Videos: Custom form cues and technique notes
- Workout Documentation: Equipment setup and form reference photos
- Coach Content Sharing: Instructional materials and technique feedback
- Program Assets: Exercise diagrams and supplemental educational content
The challenge was building storage infrastructure that works reliably across all deployment scenarios. With that foundation in place, adding visual features becomes straightforward.
Choose Your Storage, Keep Your Features
Object storage in OpenLift adapts to your deployment needs without compromising functionality. Whether you're running on a Raspberry Pi with local storage or scaling globally with R2, you get the same feature set and the same user experience.
Start with local storage, migrate to MinIO as you grow, or jump straight to R2 for global scale. Your storage choice is a deployment decision, not a feature limitation.
Check out the object storage documentation for complete setup instructions for each provider.
OpenLift is open-source fitness infrastructure that puts you in control of your training data. Star us on GitHub and join our community building the future of fitness software.
