![]()
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:
- Client requests an image with a size parameter — e.g.
image.jpg?width=300 - Proxy parses the URL, checks S3 for a cached resized version
- Cache hit → serves directly, no processing needed
- Cache miss → fetches the original from S3, resizes in-memory with libvips
- Responds to the client immediately — no waiting on the upload
- A background goroutine asynchronously uploads the resized image to S3
- 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 Speed | libvips processes images at native speed — benchmarks consistently show 4x+ gains over ImageMagick |
| Flexibility | URL-based sizing means zero pre-processing pipelines and no storage bloat from pre-generated variants |
| Cost Efficiency | Pay once to resize, cache forever — redundant processing is eliminated automatically |
| Cloud-Native | Stateless Docker containers + S3 storage means you scale out by adding instances |
| Minimal Footprint | Go 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