Skip to content

feat: compare media vendors for BYO storage support #584

@shaoster

Description

@shaoster

Problem / Motivation

Glaze currently relies on a Cloudinary-managed upload and rendering flow, but we want a path that lets users keep ownership of their media storage without making us responsible for managing their Cloudinary accounts.

Before committing to any implementation, we should evaluate which media platforms best fit Glaze along two primary dimensions:

  • image rendering capabilities that match what we use from Cloudinary today
  • ability for the user to bring their own storage

Proposed Solution

Create a comparison matrix for candidate media vendors and document how each one scores on the main behaviors we care about:

  • render as JPG
  • render with crop
  • render with custom resolution
  • bring your own storage

Comparison Matrix

Vendor Render as JPG Render with crop Render with custom resolution Bring your own storage Glaze fit Notes
Cloudinary Yes Yes Yes Low Current baseline, but not a good BYO-storage fit Strong all-around image delivery and upload support, but it is primarily a vendor-managed media platform. Sources: Upload widget, Upload API
ImageKit Yes Yes Yes High Best overall fit Explicit external-storage support for S3, Azure Blob, GCS, and web servers, plus URL-based image transformation. Sources: Image transformation, Connect external storage, AWS S3 integration
Uploadcare Yes Yes Yes Medium to High Good if we want upload workflow + S3 handoff Supports resize/crop transformations and can store or copy uploads to customer S3. Sources: Resize, crop, rotation, AWS S3 storage, Copy files to S3
Cloudflare Images + R2 Yes Yes Yes High Strong fit if we want storage-first + edge delivery Supports keeping originals on your own origin or R2 and transforming them at the edge. Sources: Introduction, Overview, Transform images
Filestack Yes Yes Yes High Flexible, but less obviously aligned Supports storage in S3, Azure, GCS, and other cloud storage targets, plus URL-based transformation. Sources: Cloud storage, Processing engine, Transformations

Cloudflare Images + R2 vs ImageKit

The main decision for Glaze is likely between Cloudflare Images + R2 and ImageKit.

1. Operation complexity of the Glaze-managed flow

  • Cloudflare Images + R2 fits naturally if Glaze stays inside Cloudflare's ecosystem. The trade-off is that the product has two distinct modes: remote transformations on outside storage, or hosted images in Cloudflare Images. Those modes bill differently, and the implementation path is easiest when we keep the system narrow.
  • ImageKit is more centralized as an image platform. It gives us a single URL-endpoint model with external storages, CDN caching, and transformations on top. That is usually simpler to reason about in the app, but it is a separate vendor with its own storage and CDN model.

2. Operational complexity of users bringing their own storage

  • Cloudflare Images + R2 is workable, but the BYO story naturally points users toward a Cloudflare-owned setup such as R2 or another origin that Cloudflare can transform. That means more account-level configuration if each potter owns their own storage.
  • ImageKit is explicitly built around external storage and URL endpoints. Its docs support multiple origins per endpoint, custom domains, and even Cloudinary migration helpers, which makes it a better fit if user-owned storage is the primary requirement.

3. Difficulty of integrating with a CDN in both cases

  • Cloudflare Images + R2 is the easiest path if we already use Cloudflare for the domain and want Cloudflare to stay the delivery layer. This is the native path.
  • ImageKit can sit behind Cloudflare, but that is a more advanced custom-CDN setup rather than the default path. ImageKit documents custom CDN integration and custom cache-key logic as an enterprise-level capability, so if we want Cloudflare in front of ImageKit, the integration is more involved.

4. Pitfalls for the future

  • Cloudflare Images + R2 may be the least flexible if we later decide we want per-poter storage ownership without becoming responsible for a Cloudflare account model. It also has two different billing shapes: remote transformations versus hosted Images storage/delivery.
  • ImageKit is operationally cleaner for BYO storage, but future custom-CDN needs may push us into enterprise features, and some advanced transformations can be treated as expensive/exceptional usage. That makes it important to keep the early design simple and avoid assuming every future capability will stay on the standard plan.

5. Pricing estimates at various use levels

These are rough estimates, not quotes. They assume a fairly normal image app with externally hosted originals and optimized renditions.

Use level Assumptions Cloudflare Images + R2 ImageKit
Small 1k originals, 1 variant each, low monthly traffic, under 10 GB storage About $0 if we stay inside the free R2 tier and the 5,000 free remote transformations/month About $0 on the free plan if bandwidth stays under 20 GB and storage stays under 3 GB
Medium 5k originals, 3 variants each, roughly 100k views/month, ~30 GB optimized delivery Roughly $5.30 total: about $5.00 for 10k billable remote transformations above the free 5k, plus a small amount of R2 storage Roughly $9/month on Lite if we exceed the free 20 GB bandwidth limit
Large 25k originals, 5 variants each, roughly 1M views/month, ~300 GB optimized delivery Roughly $61.35 total: about $60.00 for 120k billable remote transformations above the free 5k, plus a small amount of R2 storage Roughly $122.75 on Pro if we need 300 GB bandwidth: $89 base plus 75 GB overage at $0.45/GB

Recommendation

For a Glaze-style BYO-storage story, the strongest candidate appears to be ImageKit.

It gives us:

  • image optimization and delivery comparable to Cloudinary
  • external storage support without forcing users into a vendor-owned media account
  • a cleaner boundary between "Glaze manages the app" and "the user manages their storage"

If we want the most storage-first architecture, Cloudflare Images + R2 is also worth evaluating, especially if we prefer customer-owned buckets and edge delivery over a traditional media-platform model.

Migration

If we choose ImageKit for user-owned storage, the migration path should be additive rather than disruptive. The default Glaze flow can stay on Cloudinary-managed storage and signed uploads, while ImageKit becomes the opt-in path for users who want to own their media storage.

What we need to store on our side:

  • a per-user or per-workspace preference flag that says which storage backend the user wants for new uploads, defaulting to Cloudinary
  • the ImageKit connection metadata needed to serve and upload assets, at minimum the ImageKit endpoint / URL endpoint identifier and whatever source mapping is required for the chosen storage origin
  • any user-visible display label so the user can tell which storage connection is active
  • an enabled/disabled status and basic audit timestamps so we can tell whether the connection is active

What the ImageKit BYO-storage flow should look like:

  • the user connects their own external storage in ImageKit, such as S3 or another supported origin
  • Glaze stores the connection preference and the ImageKit URL endpoint identity, not the underlying storage credentials
  • for uploads, Glaze sends the user into the ImageKit upload flow for that endpoint, or asks ImageKit to accept uploads into the user-connected storage path
  • for renders, Glaze stores the returned ImageKit file URL / image identifier on the image record and renders through ImageKit URLs for that user-owned backend
  • the render layer chooses the backend per image record, so existing Cloudinary-hosted assets continue to work while new ImageKit assets use the ImageKit URL endpoint

A safe migration would likely look like this:

  • keep the current Cloudinary flow as the default for existing users and existing uploads
  • introduce ImageKit as a separate storage backend or connection type for new opt-in users
  • store per-connection metadata so Glaze can render assets from either backend without changing the rest of the piece/state model
  • keep image records backward compatible by preserving url, cloudinary_public_id, and any new ImageKit identifiers side by side while the transition is in progress
  • preserve existing Cloudinary cleanup and admin workflows until ImageKit coverage is complete, then add equivalent cleanup and verification for ImageKit-backed assets
  • migrate only new uploads first, then optionally provide a background conversion or re-linking path for users who want to move old assets over later

Acceptance Criteria

  • A written matrix exists that compares the candidate vendors across the four image/storage behaviors.
  • The matrix clearly identifies which vendors support user-owned storage versus only vendor-managed media accounts.
  • The matrix clearly identifies which vendors support JPG rendering, crop rendering, and custom resolution rendering.
  • The matrix includes a recommendation for Glaze’s default direction.
  • The recommendation explicitly calls out any vendor features that would require us to manage customer secrets or customer media accounts.

Out of Scope

  • Implementing any vendor integration
  • Changing Glaze’s current Cloudinary flow
  • Adding new secrets, credentials, or database tables
  • Building a BYO-storage onboarding UI

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions