Images, resized on the fly. Cached in S3.
imgrust is a self-hosted image proxy. Point it at your S3 bucket or any HTTP origin, describe the image you want in the URL, and get it back resized, converted and optimized. Every processed variant is written back to S3, so it is computed exactly once — across restarts and across replicas.
# 400×300 cover crop of an S3 object, served as WebP curl "https://img.example.com/{signature}/rs:fill:400:300/q:80/plain/s3://media/cats/tom.jpg@webp" # animated GIF in, animated WebP out, at half the width curl "https://img.example.com/{signature}/w:320/plain/https://cdn.example.com/party.gif@webp"
Built for load, not for demos
A small Rust server around libvips, the same image engine that powers the fastest image services. No garbage collector, no per-request processes, predictable memory.
S3 in, S3 out
Read originals with s3://bucket/key and persist processed variants to a cache bucket. Works with AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces and other S3-compatible stores.
Animated WebP & GIF
Animations stay animations. Every frame is resized and cropped, timing and looping are preserved, and GIFs are transparently served as much smaller animated WebP to browsers that accept it.
Two cache layers
A byte-bounded in-memory cache answers hot variants without touching the network. The S3 layer makes results durable and shared between every replica you run.
No thundering herd
Concurrent requests for the same uncached variant are coalesced into a single computation. CPU work runs on a bounded worker pool, and overload is shed with 503 instead of eating memory.
Signed URLs
HMAC-SHA256 signatures stop anyone from using your server as a free resize farm. Source allow-lists and limits on file size, resolution and frame count guard against abuse.
Automatic modern formats
Leave the format out of the URL and imgrust negotiates WebP or AVIF from the Accept header, with correct Vary, ETag and Cache-Control so your CDN does the rest.
imgproxy & thumbor URLs
Both URL dialects are served side by side, each with its own signature scheme: /sig/rs:fit:0:210/plain/s3://… and /sig/372x372/path.jpg. Existing URLs and client libraries keep working.
One container
A single binary in a slim Debian image, configured entirely through environment variables. Built-in health check and Prometheus metrics at /metrics.
What happens to a request
The expensive path runs once per variant. Everything after that is a lookup.
If-None-Match returns 304 immediately.X-Cache: HITX-Cache: HIT-S3X-Cache: MISSThe URL is the API
A signature, any number of processing options, and the source — either plain or base64url-encoded. Coming from thumbor? /{signature}/fit-in/600x400/smart/path/to/image.jpg works too.
- signature — HMAC-SHA256 of the rest of the path
- options — what to do with the image
- source —
s3://,http://orhttps:// - format — optional; negotiated when omitted
| Option | Meaning |
|---|---|
c:w:h:gravity | Crop the source before resizing. |
rs:type:w:h:enlarge | Resize in one go. Type is fit, fill, force or auto. |
w:400 h:300 | Width / height. 0 keeps the aspect ratio. |
s:400:300 | Width and height together. |
dpr:2 | Multiply the dimensions for high-density screens. |
el:1 | Allow upscaling. Off by default. |
g:sm | Crop gravity: ce, no, so, ea, we, corners, or sm (smart). |
| Option | Meaning |
|---|---|
q:80 | Output quality, 1–100. |
f:webp | Output format: jpg, png, webp, avif, gif. |
bl:5 | Gaussian blur with the given sigma. |
sh:1 | Sharpen with the given sigma. |
bg:ffffff | Background used when flattening transparency. |
cb:v2 | Cache buster: forces a fresh variant. |
Run it
Build the image from the repository and start it with your bucket details. That is the whole deployment.
git clone https://github.com/noneym/imgrust.git && cd imgrust docker build -t imgrust . docker run -d -p 8080:8080 \ -e IMGRUST_KEY=$(openssl rand -hex 32) \ -e IMGRUST_SALT=$(openssl rand -hex 32) \ -e IMGRUST_S3_REGION=eu-central-1 \ -e IMGRUST_S3_ACCESS_KEY=… \ -e IMGRUST_S3_SECRET_KEY=… \ -e IMGRUST_CACHE_S3_BUCKET=my-image-cache \ imgrust # or try the full local stack (imgrust + a local S3 server) docker compose up --build
Configuration
Environment variables only. These are the ones you will most likely touch; the README lists all of them.
| Variable | Default | Purpose |
|---|---|---|
IMGRUST_KEY / IMGRUST_SALT | — | Hex-encoded signing key and salt. Without a key, signatures are not checked. |
IMGRUST_THUMBOR_KEY / _BASE_URL | — | Security key and base location (e.g. s3://media/) for thumbor-style URLs. |
IMGRUST_S3_ENDPOINT | AWS | Custom endpoint for R2, MinIO, Spaces and friends. |
IMGRUST_S3_REGION | us-east-1 | Bucket region. |
IMGRUST_S3_ACCESS_KEY / _SECRET_KEY | AWS chain | Credentials. Falls back to the standard AWS credential chain (env, IAM role…). |
IMGRUST_CACHE_S3_BUCKET | — | Bucket for processed variants. Unset disables the S3 cache layer. |
IMGRUST_CACHE_S3_PREFIX | — | Key prefix inside the cache bucket. |
IMGRUST_MEM_CACHE_MB | 256 | Size of the in-memory cache. |
IMGRUST_WORKERS | CPU count | Images processed in parallel. |
IMGRUST_AUTO_WEBP / IMGRUST_AUTO_AVIF | true / false | Format negotiation from the Accept header. |
IMGRUST_ALLOWED_SOURCES | any | Comma-separated URL prefixes, e.g. s3://media/. |
IMGRUST_MAX_ANIMATION_FRAMES | 300 | Frames kept from an animation. |