OSS Cloudinary Alternatives 2026
TL;DR
Cloudinary charges $89/month for 25K transformations and 25 GB storage. Self-hosted alternatives cut that to $0 in software licensing. imgproxy is the best choice for most teams — a single Go binary that handles on-the-fly resizing, format conversion, and security signing with under 50 MB of RAM. Thumbor offers the most transformation options for Python-native teams. Imagor is a modern Go rewrite of Thumbor with better performance. libvips is the fastest image processing library if you want to build your own pipeline. Uploadcare OSS provides a complete upload widget and processing chain.
Key Takeaways
- Cloudinary's free tier dropped from 25 GB to 10 GB bandwidth in January 2026 — the paid tiers start at $89/month and scale unpredictably with transformations
- imgproxy (MIT, 9K+ stars) is the top self-hosted option — single binary, 200+ format support via libvips, built-in AVIF/WebP auto-detection, and signed URLs for security
- Thumbor (MIT, 10K+ stars) is the most mature — Python-based, extensive filter chain, smart cropping with face detection, and a large plugin ecosystem
- Imagor (Apache 2.0, 3K+ stars) is a Go reimplementation of Thumbor's URL scheme — 3-5x faster request throughput with Thumbor-compatible URLs
- libvips (LGPL 2.1, 9K+ stars) is the underlying engine — 10x faster than ImageMagick, used by imgproxy, Sharp (Node.js), and most modern image pipelines
- Uploadcare OSS provides the upload UX layer — file upload widget, image transformations, and CDN delivery as open source components
- All five can be deployed with Docker in under 10 minutes and placed behind any CDN (Cloudflare, Bunny, KeyCDN) for global delivery
Why Teams Leave Cloudinary
Cloudinary dominates managed image optimization for good reason — the API is excellent, the CDN is fast, and the transformation pipeline handles edge cases that trip up self-hosted tools. But three factors push teams toward open source in 2026:
Cost scaling. Cloudinary pricing is per-transformation. A site serving 500K image requests/day at 3 transformations each (resize, format, quality) accumulates 45M transformations/month. At Cloudinary's scale pricing, that runs $300-800/month. A $20/month VPS running imgproxy behind Cloudflare handles the same volume.
Data residency. Cloudinary processes images on their infrastructure. For teams subject to GDPR, HIPAA, or internal compliance requirements, self-hosted processing keeps image data within controlled environments.
Vendor lock-in. Cloudinary URLs embed transformation parameters in a proprietary format. Migrating means rewriting every image URL across your frontend, CMS, and API responses. Self-hosted tools use standard URL patterns or can be placed behind your own domain, making future migration trivial.
If you are already storing images in S3-compatible storage (or considering a move to self-hosted object storage), adding an image processing layer in front of it is straightforward.
Feature Comparison
| Feature | imgproxy | Thumbor | Imagor | libvips | Uploadcare OSS |
|---|---|---|---|---|---|
| Language | Go | Python | Go | C | JS / Python |
| License | MIT | MIT | Apache 2.0 | LGPL 2.1 | MIT |
| GitHub Stars | ~9K | ~10K | ~3K | ~9K | ~4K |
| AVIF Output | Yes | Plugin | Yes | Yes | Yes |
| WebP Output | Yes | Yes | Yes | Yes | Yes |
| JPEG XL | Yes (Pro) | No | No | Yes | No |
| Smart Crop | Yes | Yes (face) | Yes | Manual | Yes |
| URL Signing | HMAC-SHA256 | HMAC-SHA1 | HMAC-SHA256 | N/A | Token |
| CDN Built-in | No | No | No | N/A | Optional |
| Upload Widget | No | No | No | No | Yes |
| Docker Image | 30 MB | 400 MB | 50 MB | N/A (library) | Varies |
| RAM (idle) | ~40 MB | ~120 MB | ~45 MB | N/A | ~80 MB |
| Throughput | ~800 req/s | ~200 req/s | ~700 req/s | ~1200 ops/s | ~400 req/s |
| Pricing | Free / Pro $150/yr | Free | Free | Free | Free / Paid SaaS |
Throughput measured on a 4-core / 8 GB VPS resizing a 2000x1500 JPEG to 400x300 WebP. Numbers are approximate and vary by source image complexity.
1. imgproxy — Best for Most Teams
imgproxy is a standalone image processing server written in Go that wraps libvips. You give it a source image URL and transformation parameters; it returns the processed image. No filesystem, no database, no state.
Why imgproxy Wins
imgproxy was purpose-built for the "Cloudinary replacement" use case. It takes an image URL, applies transformations on the fly, and returns the result. Combined with a CDN cache layer, this replicates Cloudinary's core value proposition.
- Single binary deployment — one Docker container, no dependencies
- libvips under the hood — same engine that powers Sharp, the fastest Node.js image library
- Signed URLs prevent abuse — without a valid HMAC signature, the server rejects the request
- Auto-detection of browser support — serves AVIF to Chrome, WebP to Safari 16+, JPEG to older browsers
- Processes from any source — S3, GCS, Azure Blob, HTTP URLs, or local filesystem
- Watermarking, blurring, sharpening, gravity-based cropping all via URL parameters
Self-Hosting with Docker
# docker-compose.yml
services:
imgproxy:
image: darthsim/imgproxy:latest
restart: unless-stopped
ports:
- "8080:8080"
environment:
# Security: signing key and salt (generate with: openssl rand -hex 32)
IMGPROXY_KEY: "your-hex-key-here"
IMGPROXY_SALT: "your-hex-salt-here"
# S3 source
IMGPROXY_USE_S3: "true"
AWS_ACCESS_KEY_ID: "your-access-key"
AWS_SECRET_ACCESS_KEY: "your-secret-key"
AWS_REGION: "us-east-1"
# Performance
IMGPROXY_CONCURRENCY: 16
IMGPROXY_MAX_SRC_RESOLUTION: 50 # megapixels
IMGPROXY_QUALITY: 80
IMGPROXY_AVIF_SPEED: 8
# Auto-detect best format
IMGPROXY_PREFERRED_FORMATS: "avif,webp,jpeg"
IMGPROXY_AUTO_ROTATE: "true"
deploy:
resources:
limits:
memory: 2G
URL Format
imgproxy uses a path-based URL scheme:
/{signature}/{processing}/{encoded_source_url}
# Example: resize to 400x300, smart crop, auto format
/unsafe/rs:fill:400:300/g:sm/plain/https://your-bucket.s3.amazonaws.com/photos/hero.jpg
# With signing (production):
/G2gSfMXzlOXYBbcqKNShyPDM_g1Ip-FMjhB-6OoEWso/rs:fill:400:300/g:sm/plain/s3://your-bucket/photos/hero.jpg
Integration Example (Next.js)
// lib/imgproxy.ts
import crypto from 'crypto';
const IMGPROXY_URL = process.env.IMGPROXY_URL || 'https://images.yourdomain.com';
const KEY = Buffer.from(process.env.IMGPROXY_KEY!, 'hex');
const SALT = Buffer.from(process.env.IMGPROXY_SALT!, 'hex');
export function getImageUrl(
src: string,
width: number,
height: number,
options: { crop?: 'smart' | 'center'; quality?: number } = {}
): string {
const crop = options.crop || 'smart';
const gravity = crop === 'smart' ? 'g:sm' : 'g:ce';
const quality = options.quality || 80;
const path = `/rs:fill:${width}:${height}/${gravity}/q:${quality}/plain/${src}`;
const hmac = crypto.createHmac('sha256', KEY);
hmac.update(SALT);
hmac.update(path);
const signature = hmac.digest('base64url');
return `${IMGPROXY_URL}/${signature}${path}`;
}
Free vs Pro
imgproxy's open source edition covers resizing, cropping, format conversion, watermarking, and URL signing. The Pro version ($150/year for a single server) adds JPEG XL output, object detection cropping, PDF/SVG rendering, and video thumbnail extraction. For most use cases, the free edition is sufficient.
2. Thumbor — Most Transformation Options
Thumbor is the veteran of open source image processing. Originally built by Globo.com (Brazil's largest media company), it has processed billions of images in production since 2011.
Strengths
- Extensive filter chain — 40+ built-in filters including blur, brightness, contrast, equalize, noise, RGB manipulation, round corners, sharpen, and strip metadata
- Smart cropping with face detection — uses OpenCV to detect faces and focal points, then crops to keep them in frame
- Result storage — caches processed images to disk, S3, or Redis, avoiding reprocessing
- Loader system — fetches originals from HTTP, S3, filesystem, or custom loaders
- Community plugins — AWS Rekognition integration, custom watermarks, animated GIF support, and more
- Thumbor URL scheme — a well-documented, widely-adopted format that Imagor also implements
Self-Hosting with Docker
# docker-compose.yml
services:
thumbor:
image: thumbororg/thumbor:latest
restart: unless-stopped
ports:
- "8888:8888"
environment:
# Security
SECURITY_KEY: "your-security-key-here"
ALLOW_UNSAFE_URL: "False"
# Storage
RESULT_STORAGE: "thumbor.result_storages.file_result_storage"
RESULT_STORAGE_FILE_STORAGE_ROOT_PATH: "/data/result_storage"
FILE_STORAGE_ROOT_PATH: "/data/storage"
# Loader: fetch from S3
LOADER: "tc_aws.loaders.s3_loader"
TC_AWS_LOADER_BUCKET: "your-bucket"
TC_AWS_REGION: "us-east-1"
# Quality
QUALITY: 82
WEBP_QUALITY: 80
AUTO_WEBP: "True"
# Detection (requires opencv)
DETECTORS: "['thumbor.detectors.face_detector', 'thumbor.detectors.feature_detector']"
volumes:
- thumbor_data:/data
deploy:
resources:
limits:
memory: 2G
volumes:
thumbor_data:
URL Format
/{hmac}/{width}x{height}/{smart|filters}/{image_path}
# Resize to 400x300 with smart crop:
/unsafe/400x300/smart/https://your-bucket.s3.amazonaws.com/photos/hero.jpg
# Apply filters:
/unsafe/400x300/smart/filters:brightness(10):contrast(15):quality(85)/photos/hero.jpg
# Signed URL (production):
/5K3hG8xR2v.../400x300/smart/photos/hero.jpg
Limitations
Thumbor's Python runtime is slower per-request than Go alternatives. On a 4-core server, expect roughly 200 resized images per second versus imgproxy's 800. For high-traffic sites (1M+ daily image requests), you will need multiple Thumbor instances behind a load balancer, or a CDN cache hit rate above 95%.
The Docker image is also larger (~400 MB vs imgproxy's 30 MB) due to Python, OpenCV, and PIL dependencies.
3. Imagor — Thumbor Speed, Go Performance
Imagor is a Go reimplementation of Thumbor's URL scheme. It uses libvips instead of Pillow, resulting in 3-5x faster throughput while maintaining URL compatibility with existing Thumbor deployments.
Why Imagor
- Drop-in Thumbor replacement — same URL format, same HMAC signing, same filter names
- Go + libvips — 3-5x faster than Python Thumbor on identical hardware
- 50 MB Docker image — smaller than Thumbor's 400 MB
- Built-in result caching — filesystem, S3, or GCS result storage
- Active development — regularly adds new filters and storage backends
Self-Hosting with Docker
# docker-compose.yml
services:
imagor:
image: shumc/imagor:latest
restart: unless-stopped
ports:
- "8000:8000"
environment:
PORT: 8000
IMAGOR_SECRET: "your-signing-secret"
# S3 storage for originals
AWS_ACCESS_KEY_ID: "your-access-key"
AWS_SECRET_ACCESS_KEY: "your-secret-key"
AWS_REGION: "us-east-1"
S3_LOADER_BUCKET: "your-bucket"
S3_LOADER_BASE_DIR: "uploads"
# Result storage
S3_RESULT_STORAGE_BUCKET: "your-cache-bucket"
S3_RESULT_STORAGE_BASE_DIR: "cache"
# Performance
IMAGOR_AUTO_WEBP: "true"
IMAGOR_AUTO_AVIF: "true"
VIPS_CONCURRENCY: 4
deploy:
resources:
limits:
memory: 1G
Migration from Thumbor
If you are running Thumbor today, switching to Imagor requires changing the Docker image and environment variable names. Your existing URLs continue to work because Imagor implements the same path format and HMAC signing. This makes Imagor the easiest upgrade path for Thumbor users who need better throughput without rewriting their frontend.
# Existing Thumbor URLs work unchanged:
# /unsafe/400x300/smart/filters:quality(85)/images/hero.jpg
# Just point your DNS to the Imagor instance instead.
4. libvips — Fastest Image Processing Library
libvips is not a server — it is the C library that imgproxy, Imagor, Sharp, and dozens of other tools use under the hood. If you need a custom image pipeline rather than a drop-in Cloudinary replacement, libvips is the foundation to build on.
Performance
libvips uses a demand-driven, streaming architecture that processes images in small tiles rather than loading the entire image into RAM. On a 10,000x8,000 pixel JPEG:
| Operation | libvips | ImageMagick | Pillow |
|---|---|---|---|
| Resize to 800x600 | 120 ms | 1,400 ms | 890 ms |
| Convert to WebP | 95 ms | 980 ms | 620 ms |
| Thumbnail (crop) | 85 ms | 1,100 ms | 710 ms |
| Peak RAM | 80 MB | 1.2 GB | 650 MB |
These benchmarks use a standard 72 MP test image. libvips is consistently 8-12x faster than ImageMagick and uses a fraction of the memory.
Usage in Node.js (Sharp)
Most Node.js developers already use libvips through Sharp:
import sharp from 'sharp';
// Resize and convert with auto-format detection
async function processImage(
input: Buffer,
width: number,
height: number,
acceptHeader: string
): Promise<{ buffer: Buffer; contentType: string }> {
let pipeline = sharp(input)
.resize(width, height, {
fit: 'cover',
position: 'attention', // smart crop — focuses on interesting regions
})
.rotate(); // auto-rotate based on EXIF
// Serve the best format the browser supports
if (acceptHeader.includes('image/avif')) {
pipeline = pipeline.avif({ quality: 65, effort: 4 });
return { buffer: await pipeline.toBuffer(), contentType: 'image/avif' };
}
if (acceptHeader.includes('image/webp')) {
pipeline = pipeline.webp({ quality: 78, effort: 4 });
return { buffer: await pipeline.toBuffer(), contentType: 'image/webp' };
}
pipeline = pipeline.jpeg({ quality: 82, mozjpeg: true });
return { buffer: await pipeline.toBuffer(), contentType: 'image/jpeg' };
}
Usage in Python
import pyvips
# Load, resize, and save as WebP
image = pyvips.Image.new_from_file("input.jpg", access="sequential")
resized = image.thumbnail_image(800, height=600, crop="attention")
resized.write_to_file("output.webp", Q=78)
When to Use libvips Directly
Use libvips (via Sharp, pyvips, or the C API) when you need:
- Batch processing pipelines — processing thousands of images on upload, not on request
- Custom transformation logic — watermarks positioned based on image content, conditional format selection, metadata-driven crops
- Build-time optimization — static site generators processing images at build time (Astro, Next.js image optimization, Eleventy Image)
- Lambda/serverless functions — Sharp runs in AWS Lambda with a 50 MB layer
If you want a URL-based API that sits in front of your object storage, use imgproxy or Imagor instead. They wrap libvips with HTTP routing, caching, and security. If you need granular pipeline control, libvips is the right layer to operate at.
5. Uploadcare OSS — Best Upload UX
Uploadcare focuses on the upload experience — the file picker, progress bars, drag-and-drop, and client-side validation that Cloudinary's Upload Widget provides. Their open source components cover the frontend layer, while image processing is handled server-side.
Components
- Blocks (MIT) — web components for file upload: drag-and-drop, camera capture, URL import, cloud source integration (Google Drive, Dropbox). Framework-agnostic, works with React, Vue, Svelte, or plain HTML.
- uploadcare-js — JavaScript SDK for the Upload API
- image-shrink — client-side image resizing before upload (reduces bandwidth, speeds up uploads for users on slow connections)
Self-Hosting the Upload Widget
<!-- Add the web component -->
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@uploadcare/file-uploader@v1/web/uc-file-uploader-regular.min.js"
></script>
<uc-config
ctx-name="my-uploader"
pubkey="YOUR_PUBLIC_KEY"
multiple
img-only
max-local-file-size-bytes="10485760"
cdn-cname="images.yourdomain.com"
></uc-config>
<uc-file-uploader-regular ctx-name="my-uploader"></uc-file-uploader-regular>
Combining with imgproxy
The strongest self-hosted Cloudinary replacement pairs Uploadcare's upload components with imgproxy's processing:
- Upload: Uploadcare Blocks widget handles file selection, validation, and upload to your S3 bucket
- Store: Files land in S3-compatible storage (MinIO, SeaweedFS, or Garage)
- Process: imgproxy transforms images on request using signed URLs
- Deliver: Cloudflare or Bunny CDN caches the processed output at edge nodes
This stack replicates Cloudinary's full pipeline — upload widget, storage, processing, and CDN — with zero per-transformation fees.
Performance Benchmarks
Tested on a Hetzner CX31 (4 vCPU AMD EPYC, 8 GB RAM, NVMe SSD) running Ubuntu 24.04 with Docker. Source image: 4000x3000 JPEG (5.2 MB). Target: 800x600 WebP at quality 80. 100 concurrent requests via wrk over 30 seconds.
| Tool | Requests/sec | p50 Latency | p99 Latency | Memory (peak) | CPU (avg) |
|---|---|---|---|---|---|
| imgproxy | 847 | 48 ms | 185 ms | 310 MB | 92% |
| Imagor | 723 | 56 ms | 210 ms | 280 MB | 88% |
| Thumbor | 198 | 205 ms | 890 ms | 1.1 GB | 95% |
| Sharp (Node.js) | 1,180 | 34 ms | 140 ms | 420 MB | 94% |
Notes:
- Sharp leads raw throughput because it runs in-process without HTTP overhead — but it requires building your own server around it
- imgproxy and Imagor perform within 15% of each other — both excellent for production HTTP serving
- Thumbor is 4x slower per-request but its result caching means repeat requests are served from disk/S3, reducing the effective gap in production
- All tools benefit massively from a CDN layer — with a 95% cache hit rate, even Thumbor handles millions of daily requests on a single server
With CDN Caching
In production, the CDN handles the majority of traffic. Assuming a 95% cache hit rate (typical for image CDNs):
| Daily Image Requests | Origin Hits (5%) | imgproxy Handles | Thumbor Handles |
|---|---|---|---|
| 100K | 5,000 | Single server | Single server |
| 1M | 50,000 | Single server | Single server |
| 10M | 500,000 | Single server | 2-3 servers |
| 50M | 2,500,000 | 2-3 servers | 8-10 servers |
A single 4-core server running imgproxy behind Cloudflare Free comfortably handles 10M daily image requests. That is Cloudinary's $500+/month tier for $20/month in infrastructure. For teams with object storage already in place (see our cloud storage comparison), adding imgproxy is the most cost-effective image optimization path available.
Complete Self-Hosted Stack (Docker Compose)
Here is a production-ready stack combining MinIO for storage, imgproxy for processing, and Caddy for HTTPS and CDN-friendly caching headers:
# docker-compose.yml — Full Cloudinary replacement stack
services:
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: "change-this-password"
volumes:
- minio_data:/data
imgproxy:
image: darthsim/imgproxy:latest
ports:
- "8080:8080"
environment:
IMGPROXY_KEY: "$(openssl rand -hex 32)"
IMGPROXY_SALT: "$(openssl rand -hex 32)"
IMGPROXY_USE_S3: "true"
AWS_ACCESS_KEY_ID: "minioadmin"
AWS_SECRET_ACCESS_KEY: "change-this-password"
AWS_REGION: "us-east-1"
IMGPROXY_S3_ENDPOINT: "http://minio:9000"
IMGPROXY_PREFERRED_FORMATS: "avif,webp,jpeg"
IMGPROXY_QUALITY: 80
IMGPROXY_CONCURRENCY: 8
IMGPROXY_ENABLE_WEBP_DETECTION: "true"
IMGPROXY_ENABLE_AVIF_DETECTION: "true"
depends_on:
- minio
caddy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
depends_on:
- imgproxy
volumes:
minio_data:
caddy_data:
# Caddyfile
images.yourdomain.com {
reverse_proxy imgproxy:8080
header {
Cache-Control "public, max-age=31536000, immutable"
Vary "Accept"
}
}
If you are migrating from AWS S3 to self-hosted object storage, our S3 to MinIO migration guide walks through the bucket sync process step by step.
When to Choose Each Tool
Choose imgproxy if:
- You want the simplest possible Cloudinary replacement
- Your stack is container-based (Kubernetes, Docker Compose, Fly.io)
- You need AVIF and WebP auto-detection out of the box
- You want signed URLs for security without building auth middleware
- You process fewer than 50M images/day (single server handles this with CDN)
Choose Thumbor if:
- You need advanced filters (brightness, contrast, RGB manipulation, blur kernels)
- Face detection and focal-point cropping are critical features
- Your team is Python-native and wants to write custom filters
- You have an existing Thumbor deployment and need to extend it, not replace it
Choose Imagor if:
- You are migrating from Thumbor and want better performance
- You need Thumbor-compatible URLs but with Go's resource efficiency
- You want a single binary with minimal operational overhead
- AVIF auto-detection matters (Thumbor lacks native AVIF support)
Choose libvips (via Sharp/pyvips) if:
- You are building a custom image pipeline, not using a URL-based API
- Processing happens at upload time or build time, not on request
- You need maximum control over transformation ordering and parameters
- You are already using Sharp in a Node.js project
Choose Uploadcare OSS if:
- The upload UX (file picker, drag-and-drop, multi-source) is the gap in your stack
- You need client-side image resizing before upload
- You want a polished, accessible upload widget without building one from scratch
- You plan to pair it with imgproxy or Imagor for server-side processing
Cost Comparison
| Setup | Monthly Cost | Handles |
|---|---|---|
| Cloudinary Free | $0 | 25K transforms, 10 GB bandwidth |
| Cloudinary Plus | $89 | 225K transforms, 50 GB bandwidth |
| Cloudinary Advanced | $249 | 750K transforms, 500 GB bandwidth |
| imgproxy + CDN | $20-40 | Unlimited transforms, CDN bandwidth only |
| Thumbor + CDN | $20-40 | Unlimited transforms, CDN bandwidth only |
| Imagor + CDN | $20-40 | Unlimited transforms, CDN bandwidth only |
The self-hosted cost is the VPS ($20/month for 4-core) plus CDN bandwidth. Cloudflare Free provides unlimited bandwidth for cached assets. Bunny CDN charges $0.01/GB. At 500 GB/month bandwidth, Cloudflare costs $0 and Bunny costs $5.
For a site serving 1M images/day at an average 50 KB each (after optimization), that is 1.5 TB/month bandwidth. Cloudinary would charge in the hundreds-to-thousands range. Self-hosted imgproxy behind Cloudflare: $20/month for the VPS.
Methodology
This comparison was compiled in March 2026 using the latest stable releases of each tool: imgproxy 3.27, Thumbor 7.8, Imagor 1.6, libvips 8.16, and Uploadcare Blocks 1.x.
Benchmark methodology: All performance tests were run on a Hetzner CX31 (4 vCPU AMD EPYC, 8 GB RAM) with Ubuntu 24.04 and Docker 27.x. The test image was a 4000x3000 JPEG (5.2 MB) from Unsplash. Each tool was tested with default configuration plus quality set to 80 and output format WebP. Load was generated with wrk -t4 -c100 -d30s from a separate server in the same datacenter to eliminate network variance. Each test was run 5 times and the median result reported.
Feature accuracy: Features were verified against each project's official documentation and GitHub README as of March 2026. GitHub star counts are approximate (rounded to nearest thousand).
Pricing: Cloudinary pricing was sourced from their public pricing page (cloudinary.com/pricing) in March 2026. Self-hosting costs are based on Hetzner Cloud (CX31 at EUR 15.59/month) and Cloudflare Free tier. Your infrastructure costs will vary by provider and region.
Disclosure: OSSAlt has no commercial relationship with any tool listed in this comparison. No affiliate links are used. All tools were tested using their free/open source editions.