Skip to content
← All writing
Article · 2 min read Go services By Abidh Muhsin

Building a High-Performance Image Resizer Proxy with Go

Learn how to build a containerized image resizing service using Go and libvips for on-demand image optimization with S3 caching.

Resizer Proxy

Overview

Serving images efficiently is one of those problems that looks trivial until it isn’t — and then it’s suddenly responsible for 60% of your page load time. This article explores building a high-performance image resizing microservice that handles on-demand optimization with intelligent caching, written in Go and powered by libvips.

No pre-generating a dozen sizes per image. No bloated Lambda functions. Just a lean, fast proxy that resizes exactly what’s needed, when it’s needed.

The Solution

The core of this approach is a containerized proxy server built in Go that sits between your app and your image storage:

  • Intercepts image requests — Acts as a transparent middleware layer; clients just add a ?width= param
  • Resizes on-demand — No pre-processing pipelines, no storage sprawl
  • Caches in S3 — First resize pays the cost; every subsequent request is instant
  • Leverages libvips — Nearly 4x faster than ImageMagick with significantly lower memory usage
  • Scales with Docker — Stateless design means horizontal scaling is trivial

Quick Architecture Diagram

flowchart TD
    Client(["`**Browser / Mobile App**`"])
    CDN["`**CDN**
    Cloudflare / Fastly`"]
    LB["`**Load Balancer**`"]

    subgraph Cluster["🐳 Go Resize Proxy Cluster"]
        direction TB
        A[Instance 1]
        B[Instance 2]
        C[Instance N]
    end

    Parser["`**Request Parser**
    Extract URL + Width`"]
    CacheCheck{"`Resized image
    in cache?`"}
    ObjectStore[("`**Object Storage**
    S3 / MinIO`")]
    Fetcher["`**Fetch Original**
    from S3`"]
    Processor["`**bimg Processor**`"]
    Libvips["`⚡ libvips Engine`"]
    AsyncUploader["`**Async Upload Worker**`"]

    Client -->|HTTPS request| CDN
    CDN -->|Cache miss| LB
    LB --> A & B & C
    A & B & C --> Parser

    Parser --> CacheCheck

    CacheCheck -->|"✅ Hit"| ObjectStore
    ObjectStore -->|Cached image| CDN
    CDN -->|Serve| Client

    CacheCheck -->|"❌ Miss"| Fetcher
    Fetcher -->|Original image| ObjectStore
    ObjectStore --> Processor
    Processor <-->|Process| Libvips
    Processor -->|Resized image| CDN
    Processor -.->|"async"| AsyncUploader
    AsyncUploader -.->|"cache for next time"| ObjectStore

    class Client client
    class CDN,LB infra
    class A,B,C cluster
    class CacheCheck decision
    class ObjectStore storage
    class Fetcher,Processor,Libvips processor
    class AsyncUploader async

Request flow, step by step:

  1. Client requests an image with a size parameter — e.g. image.jpg?width=300
  2. Proxy parses the URL, checks S3 for a cached resized version
  3. Cache hit → serves directly, no processing needed
  4. Cache miss → fetches the original from S3, resizes in-memory with libvips
  5. Responds to the client immediately — no waiting on the upload
  6. A background goroutine asynchronously uploads the resized image to S3
  7. All future requests for that size hit the cache

The async upload in step 6 is a small but meaningful detail — it keeps response latency low even on the first request for a new size.

Key Benefits

Raw Speedlibvips processes images at native speed — benchmarks consistently show 4x+ gains over ImageMagick
FlexibilityURL-based sizing means zero pre-processing pipelines and no storage bloat from pre-generated variants
Cost EfficiencyPay once to resize, cache forever — redundant processing is eliminated automatically
Cloud-NativeStateless Docker containers + S3 storage means you scale out by adding instances
Minimal FootprintGo compiles to a small, self-contained binary with low memory overhead

When to Use This Approach

This pattern is a good fit when:

  • You’re running a high-traffic, image-heavy product (e-commerce, media, SaaS dashboards)
  • You need to support dynamic sizing without managing multiple upload variants
  • You want to reduce storage costs by generating sizes on-demand rather than at upload time
  • You’re building toward a microservice architecture and want image handling decoupled from your main app
  • You’re on a cloud budget and want to minimize redundant compute

Read the Full Article

Originally published on the QBurst Engineering Blog — written during my time at QBurst.

The full article goes deep on implementation — from project setup and request parsing to the libvips integration and production Docker configuration:

📖 Creating a High-Performance Image Resizer Proxy as a Dockerized Go Microservice

Read next

Closest to this one
  1. 01Image-resizer proxy ↗Open source · 2022
  2. 02postgres-pgbackrest-docker ↗Open source · 2022
  3. 03Decorator-Based Selective Loader for Angular HTTP RequestsArticle · 2 min