Skip to content

Storage

OpenCauldron needs a place to store reference images you upload and the media files it generates. Two backends are available: a local filesystem backend for development and a Cloudflare R2 backend for production. The backend is selected at startup via a single environment variable.


Set STORAGE_PROVIDER in your .env file:

Terminal window
# Local filesystem — easy for development, not accessible to external APIs
STORAGE_PROVIDER="local"
# Cloudflare R2 — required for production and for image-to-video generation
STORAGE_PROVIDER="r2"

If STORAGE_PROVIDER is not set, the app defaults to r2.


The local backend writes files to an uploads/ directory at the project root and serves them through the built-in /api/uploads/ route. No credentials are required.

This is the right choice when you are running OpenCauldron locally and only generating images. It is not suitable for production and it will not work for image-to-video generation (see below).

Why local storage breaks image-to-video

Image-to-video models (Veo 3, Runway, Kling, Hailuo, Ray) require a publicly accessible URL for the reference image — the AI provider’s API fetches the image directly from that URL. Local storage URLs are relative paths like /api/uploads/... that are only reachable inside your own machine. External APIs cannot reach them.

If you want to use image-to-video, you must use a storage backend that returns a public URL. Cloudflare R2 with a public bucket (or any other publicly accessible object storage) satisfies this requirement.


The R2 backend uses the AWS S3-compatible API. Configure it with five environment variables:

Terminal window
STORAGE_PROVIDER="r2"
R2_ACCOUNT_ID="your-cloudflare-account-id"
R2_ACCESS_KEY_ID="your-r2-access-key-id"
R2_SECRET_ACCESS_KEY="your-r2-secret-access-key"
R2_BUCKET_NAME="cauldron"
R2_PUBLIC_URL="https://your-bucket.your-domain.com" # optional but strongly recommended

R2_PUBLIC_URL is optional but strongly recommended. When set, every uploaded file gets a stable public URL at {R2_PUBLIC_URL}/{key}. When omitted, the backend falls back to presigned URLs that expire after one hour — this is fine for viewing but breaks any workflow that stores the URL for later use (such as referencing a generated image when creating a video).

To set up R2:

  1. Go to Cloudflare Dashboard > R2 Object Storage and create a bucket.
  2. Under Manage R2 API Tokens, create a token with read and write permissions on the bucket. Copy the Account ID, Access Key ID, and Secret Access Key.
  3. To enable public access, connect a custom domain to the bucket under the bucket’s Settings > Custom Domains tab. Use that domain as R2_PUBLIC_URL.

The upload endpoint at POST /api/uploads enforces the following limits:

ConstraintValue
Maximum file size10 MB
Allowed typesPNG, JPEG, WebP, GIF

Requests that exceed the size limit or use an unsupported type are rejected with a 400 response before any storage operation occurs.


Files are stored under two key prefixes:

PrefixUsed for
user-uploads/{userId}/{timestamp}-{nanoid}.{ext}Reference images uploaded by users via the upload endpoint
assets/{userId}/{timestamp}-{nanoid}.{ext}Generated images and videos produced by the app

Thumbnails are stored alongside their originals with a _thumb.webp suffix — for example, assets/{userId}/1234567890-abcde12345.png gets a thumbnail at assets/{userId}/1234567890-abcde12345_thumb.webp.


Every image uploaded or generated by the app gets a thumbnail automatically. Thumbnails are:

  • Resized to 400px wide (height scaled proportionally)
  • Encoded as WebP at 80% quality
  • Stored alongside the original file under the same key prefix

Thumbnails are generated using sharp. No additional configuration is required — thumbnail generation is always on.


The storage system is built around a three-method interface:

export interface StorageBackend {
upload(buffer: Buffer, key: string, contentType: string): Promise<string>;
getUrl(key: string): Promise<string>;
delete(key: string): Promise<void>;
}
  • upload — stores a file buffer at the given key and returns the file’s public URL
  • getUrl — returns the URL for an already-stored key (may be a presigned URL if the backend requires auth)
  • delete — removes the file at the given key

To add a new backend (Amazon S3, Google Cloud Storage, Azure Blob Storage, or any other object store):

  1. Create a new file in src/lib/storage/, e.g. src/lib/storage/s3.ts.
  2. Implement the StorageBackend interface and export your backend instance.
  3. In src/lib/storage/index.ts, add a condition in getBackend() to return your implementation when STORAGE_PROVIDER matches your chosen identifier.

The rest of the application — uploads, asset generation, thumbnails, URL retrieval — will use your backend automatically.