·8 min read

Extreme Engineering for Startups: Enterprise Image Scaling at No Cost

How we designed a production platform to serve millions of images and terabytes of bandwidth for tens of dollars per month instead of building an expensive image infrastructure.


When building a startup, infrastructure decisions are often made backwards. You start with a stack of managed SaaS products:

"We need image transformations. Let me sign up for Cloudinary or Imgix."
"We need storage. Let me buy AWS S3."
"We need a CDN. Let me set up CloudFront."

Before long, you’ve assembled five distinct paid services to solve what should be a straightforward engineering task. For a bootstrapped or early-stage startup, this is dangerous—not because those services are bad, but because you can accidentally build an architecture whose costs scale directly with your success.

When building our architecture, the objective was aggressive:

Serve millions of marketplace listing images and terabytes of bandwidth while keeping total monthly infrastructure costs in the tens of dollars—rather than hundreds or thousands.

My target wasn't "find the cheapest image SaaS." It was:

"Design the image workload so that there is almost nothing expensive left to pay for."


Where Image Infrastructure Costs Money

To engineer out the expense, you first need to break down where image pipeline costs originate:

  • Storage: Scales with total bytes of original + derivative images stored.
  • Transformations: Scales with the number of images processed.
  • Compute: Server CPU/RAM time spent decoding and encoding images.
  • Bandwidth / Egress: Scales with the number of times images are viewed by users.

In a marketplace platform, bandwidth dwarfs every other cost category. One seller might upload 5 listing photos. Those photos could be viewed 100 times, 10,000 times, or 1,000,000 times. If your infrastructure provider charges $0.08–$0.12 per GB for egress, your costs scale linearly with user engagement—penalizing your growth.

Traditional Model:
High User Engagement → Terabytes of Egress → Massive Infrastructure Bill

Our goal was to invert this curve:

Zero-Egress Model:
High User Engagement → Edge Cache Hits → $0 Egress Overhead

Eliminating Bandwidth Egress with Cloudflare R2

The foundation of our storage economics is Cloudflare R2. Unlike AWS S3 or Google Cloud Storage, Cloudflare R2 does not charge for outbound Internet egress.

R2 Standard pricing charges:

  • Storage: ~$0.015 / GB-month
  • Class A Operations (Writes/Lists): $4.50 / million
  • Class B Operations (Reads): $0.36 / million
  • Egress: $0.00

By pairing R2 with Cloudflare's global edge network, serving 50 TB of image traffic per month incurs zero egress charges. Bandwidth is effectively eliminated as a variable cost component.


Client-Side Pre-Compression & Direct Uploads

Before an image even reaches object storage, we attack payload size on the client side. Raw mobile camera uploads are often 5–12 MB.

Pre-Upload Compression

Using client-side WebAssembly, Canvas API, or native mobile codecs (React Native image resizers), the client app pre-compresses raw photos down to ~1–2 MB before upload. This cuts mobile network latency and saves storage before any backend component is invoked.

Direct-to-R2 Uploads

If users upload multi-megabyte raw photos to our application server (VPS) only for the server to forward those bytes to R2, we waste server CPU, memory, and inbound bandwidth. Instead, the monolith generates presigned upload authorizations, allowing clients to upload directly to R2.

sequenceDiagram
    participant User as Client App
    participant App as Core Backend API
    participant R2 as Cloudflare R2

    User->>App: Request Upload URL
    App->>App: Authenticate & Authorize
    App-->>User: Presigned Upload URL
    User->>R2: Direct HTTP PUT (Pre-compressed Image)
    R2-->>User: Upload Success (200 OK)

The application server never proxies raw upload payload bytes.


Shifting from SaaS Transformations to Self-Hosted libvips

Many teams turn to Cloudinary or ImageKit because they offer dynamic URL-based image transformation (?w=400&h=300&q=80). But generality comes at a high price per transformation.

We asked a fundamental question: What image transformations does a marketplace actually need?

A marketplace listing requires a predictable set of formats:

  • Thumbnail (thumbnail/ variant — e.g. 400px WebP)
  • Detail / Full (detail/ variant — e.g. 1200px WebP)

We don't need arbitrary width or quality parameters on every request. Since the target variants are known upfront, why pay a SaaS provider every time an image is requested?

Instead of paying a third party, we process images asynchronously using libvips—an ultra-fast, low-memory C library for image processing.

Why Keep the Monolith Architecture?

Rather than creating a standalone image microservice, we use a background worker process inside our existing deployment structure. The main monolith handles authentication, database ownership, and upload authorization, while a lightweight worker executes libvips transformations in the background.

flowchart LR
    ORIGINAL[Uploaded Original] --> VIPS[libvips Worker]
    VIPS --> THUMB[Thumbnail WebP]
    VIPS --> DETAIL[Detail WebP]

Event-Driven Asynchronous Processing & Fallbacks

Once an image lands in R2, we trigger processing asynchronously using Cloudflare R2 Event Notifications and Cloudflare Queues.

flowchart LR
    R2[R2 Storage] -->|ObjectCreated| EVENT[Event Notification]
    EVENT --> QUEUE[Cloudflare Queue]
    QUEUE --> WORKER[Image Worker]
    WORKER -->|GET Original| R2
    WORKER -->|libvips Resize| VIPS[libvips Engine]
    VIPS -->|PUT WebP Variants| R2
  • R2 fires an ObjectCreated event containing the object key.
  • The event is pushed to a Cloudflare Queue.
  • Our background worker pulls the message, downloads the original image once, runs libvips to output optimized WebP thumbnail and detail variants, and writes them back to R2.

Instant Rendering & Fallbacks

During the brief 1–2 second window before libvips finishes generating WebP variants, the client UI instantly renders a lightweight local BlurHash or falls back to serving the uploaded original directly, ensuring zero perceived UI latency for users.


Maximize Edge Caching & Storage Lifecycle Rules

To ensure images can be cached indefinitely across browsers, mobile apps, and Cloudflare edge nodes, we enforce immutable content paths:

/images/thumbnail/img_0bf1830bff53f64c.webp
/images/detail/img_0bf1830bff53f64c.webp

When an image is updated, a new unique ID is generated. The original object is never mutated in place. This allows us to set aggressive HTTP cache headers:

Cache-Control: public, max-age=31536000, immutable
flowchart LR
    USER[Client App] --> EDGE[Cloudflare Edge CDN]
    EDGE -->|Cache HIT| USER
    EDGE -->|Cache MISS| R2[Cloudflare R2]
    R2 -->|Populate Cache| EDGE

R2 Storage Lifecycle Archiving

To optimize storage costs even further as the platform grows, we configure R2 Object Lifecycle rules to automatically expire uncompressed raw originals after 30 days while retaining the optimized WebP variants indefinitely. This slashes long-term storage requirements by up to ~50%.


The Target Architecture

By combining direct uploads, asynchronous libvips processing, zero-egress R2 storage, and immutable edge caching, the complete image system looks like this:

flowchart TB
    USER[Millions of Users / Clients]

    subgraph CLOUDFLARE[Cloudflare Infrastructure]
        CDN[CDN / Edge Cache]
        R2[Cloudflare R2 Storage]
        EVENT[R2 ObjectCreated Event]
        QUEUE[Cloudflare Queue]
    end

    subgraph MONOLITH[Core Backend Infrastructure]
        APP[App Monolith / Auth API]
        WORKER[Background Image Worker]
        VIPS[libvips Library]
    end

    USER -->|View Image| CDN
    CDN -->|Cache Miss Only| R2

    USER -->|Request Upload| APP
    APP -->|Upload Authorization| USER
    USER -->|Direct PUT| R2

    R2 --> EVENT
    EVENT --> QUEUE
    QUEUE --> WORKER
    WORKER -->|Fetch Original| R2
    WORKER --> VIPS
    VIPS --> WORKER
    WORKER -->|Store WebP Variants| R2

Division of Responsibilities

Layer Component Responsibility
Auth & Business Rules Core App Monolith User authentication, permission checks, presigned URLs
Storage Cloudflare R2 Object storage for raw originals and WebP variants
Queueing Cloudflare Queues Event delivery for newly created objects
Transformation libvips Worker One-time asynchronous WebP encoding
Delivery & CDN Cloudflare CDN Global caching and edge distribution

Financial Cost Comparison

To demonstrate the real-world impact, let's compare serving 10 Million Image Views / Month (~15 TB Bandwidth, 50,000 New Uploads) across architectures:

Cost Element Traditional Setup (S3 + Cloudinary + CloudFront) Zero-Egress Architecture (R2 + libvips + Cloudflare CDN)
Image Transformations ~$150.00 / mo (Cloudinary / ImageKit tier) $0.00 (Processed locally once via libvips)
Bandwidth / Egress (15 TB) ~$1,200.00 / mo ($0.08 / GB CloudFront) $0.00 (Zero R2 Egress fee)
Storage (1 TB dataset) ~$23.00 / mo (AWS S3 Standard) ~$15.00 / mo (Cloudflare R2)
Read Operations (Class B) ~$4.00 / mo ~$0.36 / mo
Total Monthly Cost ~$1,377.00 / month ~$15.36 / month

Key Takeaways for Startup Founders

  • Compress Before Uploading: Pre-compress raw photos on client devices to minimize upload bandwidth and storage overhead.
  • Decouple Bandwidth from Egress Costs: Select infrastructure where your largest growth dimension (user views) doesn't drive exponential bills.
  • Transform Once, Serve N Times: Eliminate runtime transformation logic from the read path.
  • Don't Proxy Upload Bytes: Let clients upload directly to object storage using presigned tokens.
  • Use Immutable Cache Keys & Storage Lifecycle Rules: Set long-lived Cache-Control: immutable headers and auto-expire unneeded originals.

Extreme engineering isn't about finding a cheaper way to pay for an expensive architecture. It's about designing the architecture so you never have to pay for the expensive part in the first place.