Object Storage Providers
Overview
OpenLift’s file uploads (profile photos, exercise media, progress photos, app assets) flow through src/services/objectStorage/objectStorage.service.ts. The service:
- Uses the provider factory (
providerFactory.ts) to attach one of three providers: Cloudflare R2, MinIO, or Local filesystem. - Manages three logical buckets (
userAssets,exerciseAssets,appAssets) plus retention for profile/progress photos. - Issues signed URLs for client uploads/downloads and enforces MIME/size limits on server-side uploads.
- Shares configuration exclusively through environment variables defined in
src/config/objectStorage.config.ts.
Set OBJECT_STORAGE_PROVIDER to control which backend is loaded (R2, MINIO, or LOCAL). Every other variable is ignored unless the matching provider is active, so you can keep multiple configs in .env during migrations.
Core Settings
| Variable | Default | Description |
|---|---|---|
OBJECT_STORAGE_PROVIDER | R2 | Selects the provider (R2, MINIO, LOCAL). |
SIGNED_URL_DEFAULT_EXPIRATION_SECONDS | 604800 | Expiration for download URLs (7d). |
MAX_SERVER_UPLOAD_FILE_SIZE_BYTES | 10485760 | Server-side upload cap (10 MB). |
ALLOWED_SERVER_UPLOAD_MIME_TYPES | image/jpeg,image/png,image/gif | Comma-separated MIME whitelist. |
Cloudflare R2 (Recommended)
R2 uses the S3-compatible API; buckets must be created manually in the Cloudflare dashboard. The provider never attempts to create/delete buckets.
| Variable | Required? | Description |
|---|---|---|
R2_ACCOUNT_ID | ✅ | Cloudflare account ID. |
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY | ✅ | API token pair with Object Storage write scope. |
R2_USER_ASSETS_BUCKET_NAME | ✅ | Bucket for user uploads (progress photos, profile pics). |
R2_EXERCISE_ASSETS_BUCKET_NAME | ✅ | Bucket for exercise/category media. |
R2_APP_ASSETS_BUCKET_NAME | optional | Bucket for marketing/app assets. |
R2_ENDPOINT_OVERRIDE | optional | Custom endpoint (e.g., Wrangler tunnels or dev env). |
Checklist
- Create the buckets in Cloudflare R2 first—names must match the variables above.
- Create an API token with access to the buckets, copy the key pair into
.env. - Ensure your CDN rules or zero-trust policies allow the OpenLift service to reach
https://<ACCOUNT>.r2.cloudflarestorage.com. - Restart the service; the provider will validate credentials on boot and log
R2 provider initialized successfully.
MinIO (Self-hosted S3)
MinIO is ideal for on-prem or air-gapped installs. The provider auto-creates buckets during initialize() if they are missing.
| Variable | Required? | Description |
|---|---|---|
MINIO_ENDPOINT | ✅ | Hostname/IP of the MinIO server. |
MINIO_PORT | default 9000 | S3 port (enable TLS on 443 if fronted). |
MINIO_ACCESS_KEY / MINIO_SECRET_KEY | ✅ | Access credentials (32+ chars recommended). |
MINIO_USE_SSL | default false | Set true when serving over HTTPS. |
MINIO_USER_ASSETS_BUCKET_NAME | ✅ | Bucket for user uploads. |
MINIO_EXERCISE_ASSETS_BUCKET_NAME | ✅ | Bucket for exercise media. |
MINIO_APP_ASSETS_BUCKET_NAME | optional | App asset bucket (optional). |
Checklist
- Leave buckets blank—OpenLift will call
makeBucketif they do not exist. - Configure TLS or run behind a reverse proxy; set
MINIO_USE_SSL=truewhen applicable. - Ensure network access between OpenLift and MinIO; failures appear as
MinIO provider initialization failed. - Review MinIO IAM policies so the supplied key can
PutObject,GetObject,DeleteObject, andPresignedGetObject.
Local Storage (Development)
The local provider keeps uploads on disk and signs URLs using an HMAC secret. Use this mode for local development or automated tests; it is not recommended for multi-node environments.
| Variable | Description |
|---|---|
LOCAL_STORAGE_BASE_PATH | Absolute/relative base directory where all buckets live (e.g., ./data/storage). |
LOCAL_USER_ASSETS_BUCKET, LOCAL_EXERCISE_ASSETS_BUCKET, LOCAL_APP_ASSETS_BUCKET | Directory names under basePath. Defaults: user-assets, exercise-assets, app-assets. |
LOCAL_STORAGE_FILE_MODE, LOCAL_STORAGE_DIR_MODE | Octal permissions; defaults 644 and 755. |
LOCAL_STORAGE_ALLOWED_EXTENSIONS | Optional comma-separated whitelist (.jpg,.png,.webp). Leave empty to skip extension filtering. |
LOCAL_STORAGE_MAX_FILE_SIZE | Max upload size in bytes (default 100 MB). |
LOCAL_STORAGE_PREVENT_PATH_TRAVERSAL | true to guard against ../ keys. |
LOCAL_STORAGE_BASE_URL | Base URL the API should embed inside signed links (e.g., http://localhost:3333/storage). |
LOCAL_STORAGE_SECRET | HMAC secret used to sign URLs—set this even in dev. |
LOCAL_STORAGE_DEFAULT_EXPIRATION | Signed URL expiration (seconds). |
When the provider initializes it creates each bucket directory and applies the provided permission mask. Make sure the OS user running OpenLift has read/write privileges.
Signed URLs & Upload Policies
- Download URLs default to 7 days (
SIGNED_URL_DEFAULT_EXPIRATION_SECONDS) and can be overridden per call. - Upload URLs use the same expiration unless you set
SIGNED_URL_UPLOAD_EXPIRATION_SECONDS(supported by the service even if not listed in.env). - Server-side uploads (from admin panels/scripts) respect
MAX_SERVER_UPLOAD_FILE_SIZE_BYTESandALLOWED_SERVER_UPLOAD_MIME_TYPES. For local dev, align these withLOCAL_STORAGE_MAX_FILE_SIZE.
Retention Toggles
Retention is enforced by the services that own each asset (progress photos, profile photos, workout imagery). Configure counts and auto-cleanup flags via environment variables:
| Variable | Default | Purpose |
|---|---|---|
PROGRESS_PHOTOS_RETENTION_COUNT | 10 | Keep last N progress photos per angle. |
PROGRESS_PHOTOS_AUTO_CLEANUP | true | Automatically archive/delete older photos. |
PROFILE_PICTURES_RETENTION_COUNT | 5 | Keep last N profile pictures. |
PROFILE_PICTURES_AUTO_CLEANUP | true | Cleanup extra profile images. |
WORKOUT_TEMPLATE_IMAGES_RETENTION_COUNT / _AUTO_CLEANUP | 10 / true | Template artwork retention. |
WORKOUT_PROGRAM_IMAGES_RETENTION_COUNT / _AUTO_CLEANUP | 10 / true | Program artwork retention. |
APP_IMAGES_RETENTION_COUNT / _AUTO_CLEANUP | 20 / true | App-specific assets. |
Operational Tips
- Switching providers: update
OBJECT_STORAGE_PROVIDERand the new provider’s variables, then redeploy. The service reconnects at boot; migrate data between buckets manually. - Health checks: failures surface during startup (
ConfigurationError). Watch logs fromObjectStorageServicefor upload/download errors in runtime. - Signed URL domains: make sure the URL front-end (Cloudflare Worker, CDN, local static path) matches the
baseUrlyou expose to clients; otherwise uploads will succeed but downloads will 404. - Security: rotate API keys periodically. For Local mode, treat
LOCAL_STORAGE_SECRETlike a password—signed URLs are just HMAC tokens.
Need a quick reference? Use this command to verify which provider is live:
OBJECT_STORAGE_PROVIDER=R2 node -e "console.log(require('./dist/config/objectStorage.config').createObjectStorageConfig())"
Adjust configs as needed and restart the service—the provider factory will take it from there.