6/8 - Public Beta (Discord)
|See Changelog
Skip to main content

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

VariableDefaultDescription
OBJECT_STORAGE_PROVIDERR2Selects the provider (R2, MINIO, LOCAL).
SIGNED_URL_DEFAULT_EXPIRATION_SECONDS604800Expiration for download URLs (7d).
MAX_SERVER_UPLOAD_FILE_SIZE_BYTES10485760Server-side upload cap (10 MB).
ALLOWED_SERVER_UPLOAD_MIME_TYPESimage/jpeg,image/png,image/gifComma-separated MIME whitelist.

R2 uses the S3-compatible API; buckets must be created manually in the Cloudflare dashboard. The provider never attempts to create/delete buckets.

VariableRequired?Description
R2_ACCOUNT_IDCloudflare account ID.
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEYAPI token pair with Object Storage write scope.
R2_USER_ASSETS_BUCKET_NAMEBucket for user uploads (progress photos, profile pics).
R2_EXERCISE_ASSETS_BUCKET_NAMEBucket for exercise/category media.
R2_APP_ASSETS_BUCKET_NAMEoptionalBucket for marketing/app assets.
R2_ENDPOINT_OVERRIDEoptionalCustom endpoint (e.g., Wrangler tunnels or dev env).

Checklist

  1. Create the buckets in Cloudflare R2 first—names must match the variables above.
  2. Create an API token with access to the buckets, copy the key pair into .env.
  3. Ensure your CDN rules or zero-trust policies allow the OpenLift service to reach https://<ACCOUNT>.r2.cloudflarestorage.com.
  4. 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.

VariableRequired?Description
MINIO_ENDPOINTHostname/IP of the MinIO server.
MINIO_PORTdefault 9000S3 port (enable TLS on 443 if fronted).
MINIO_ACCESS_KEY / MINIO_SECRET_KEYAccess credentials (32+ chars recommended).
MINIO_USE_SSLdefault falseSet true when serving over HTTPS.
MINIO_USER_ASSETS_BUCKET_NAMEBucket for user uploads.
MINIO_EXERCISE_ASSETS_BUCKET_NAMEBucket for exercise media.
MINIO_APP_ASSETS_BUCKET_NAMEoptionalApp asset bucket (optional).

Checklist

  1. Leave buckets blank—OpenLift will call makeBucket if they do not exist.
  2. Configure TLS or run behind a reverse proxy; set MINIO_USE_SSL=true when applicable.
  3. Ensure network access between OpenLift and MinIO; failures appear as MinIO provider initialization failed.
  4. Review MinIO IAM policies so the supplied key can PutObject, GetObject, DeleteObject, and PresignedGetObject.

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.

VariableDescription
LOCAL_STORAGE_BASE_PATHAbsolute/relative base directory where all buckets live (e.g., ./data/storage).
LOCAL_USER_ASSETS_BUCKET, LOCAL_EXERCISE_ASSETS_BUCKET, LOCAL_APP_ASSETS_BUCKETDirectory names under basePath. Defaults: user-assets, exercise-assets, app-assets.
LOCAL_STORAGE_FILE_MODE, LOCAL_STORAGE_DIR_MODEOctal permissions; defaults 644 and 755.
LOCAL_STORAGE_ALLOWED_EXTENSIONSOptional comma-separated whitelist (.jpg,.png,.webp). Leave empty to skip extension filtering.
LOCAL_STORAGE_MAX_FILE_SIZEMax upload size in bytes (default 100 MB).
LOCAL_STORAGE_PREVENT_PATH_TRAVERSALtrue to guard against ../ keys.
LOCAL_STORAGE_BASE_URLBase URL the API should embed inside signed links (e.g., http://localhost:3333/storage).
LOCAL_STORAGE_SECRETHMAC secret used to sign URLs—set this even in dev.
LOCAL_STORAGE_DEFAULT_EXPIRATIONSigned 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_BYTES and ALLOWED_SERVER_UPLOAD_MIME_TYPES. For local dev, align these with LOCAL_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:

VariableDefaultPurpose
PROGRESS_PHOTOS_RETENTION_COUNT10Keep last N progress photos per angle.
PROGRESS_PHOTOS_AUTO_CLEANUPtrueAutomatically archive/delete older photos.
PROFILE_PICTURES_RETENTION_COUNT5Keep last N profile pictures.
PROFILE_PICTURES_AUTO_CLEANUPtrueCleanup extra profile images.
WORKOUT_TEMPLATE_IMAGES_RETENTION_COUNT / _AUTO_CLEANUP10 / trueTemplate artwork retention.
WORKOUT_PROGRAM_IMAGES_RETENTION_COUNT / _AUTO_CLEANUP10 / trueProgram artwork retention.
APP_IMAGES_RETENTION_COUNT / _AUTO_CLEANUP20 / trueApp-specific assets.

Operational Tips

  • Switching providers: update OBJECT_STORAGE_PROVIDER and 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 from ObjectStorageService for upload/download errors in runtime.
  • Signed URL domains: make sure the URL front-end (Cloudflare Worker, CDN, local static path) matches the baseUrl you expose to clients; otherwise uploads will succeed but downloads will 404.
  • Security: rotate API keys periodically. For Local mode, treat LOCAL_STORAGE_SECRET like 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.