# imagor: a thumbor-shaped image server that does the work in libvips

> A Go image processing server and library built on libvips streaming, with thumbor URL syntax, a large filter set, pluggable loaders and storage, and a Docker image that runs in one command.

**cshum/imagor** — Fast, secure image processing server and Go library, using libvips

- Repository: https://github.com/cshum/imagor
- Website: https://imagor.net
- Stars: 4,033 · Forks: 176
- Language: Go
- License: Apache-2.0
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/cshum-imagor

## One container command and a URL that looks like thumbor

The README's quick start is a single `docker run` line with no config file, no volume and no build step:

```bash
docker run -p 8000:8000 shumc/imagor -imagor-unsafe -imagor-auto-webp
```

Two flags do most of the work here. `-imagor-unsafe` tells the server it may fetch arbitrary upstream URLs, which is what you want for a first look and exactly what you do not want on a public endpoint. `-imagor-auto-webp` serves a WebP variant to clients that accept one, negotiated from the request rather than encoded into the URL.

What makes the project interesting is that the URL syntax is thumbor's. The README calls it a high-performance drop-in replacement, and the sample URLs show what that means in practice. Dimensions come first, then filters:

```text
http://localhost:8000/unsafe/fit-in/200x200/filters:fill(white)/https://raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png
http://localhost:8000/unsafe/200x200/smart/filters:fill(white):format(jpeg):quality(80)/https://raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png
http://localhost:8000/unsafe/30x40:100x150/filters:fill(cyan)/raw.githubusercontent.com/cshum/imagor/master/testdata/dancing-banana.gif
```

That third example is the detail worth noticing. It encodes a bounding box, `30x40:100x150`, rather than a single target size, and the server decides how to fit the image inside it. Reading a resize path from a thumbnail component therefore means understanding a small grammar, not writing a resize function per call site.

## Streaming through libvips rather than shelling out per request

The performance claim in the README is specific: libvips is typically 4 to 8x faster than the quickest ImageMagick settings, and imagor adds libvips streaming so pipelines run in parallel with high network throughput. The Go binding is `github.com/cshum/vipsgen`, pinned at v1.3.11 in `go.mod`, which matters because generated bindings track the C library closely.

The container is built around that dependency rather than around a generic base image. The `Dockerfile` starts from `ghcr.io/cshum/imagor-base:vips8.18.5-r14`, a prebuilt image with libvips already compiled, and the `Makefile` keeps that same tag in `IMAGOR_BASE_IMAGE`. The runtime stage links jemalloc in and preloads it:

```text
ENV MALLOC_ARENA_MAX=2
ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so
ENV VIPS_VECTOR=167772160
ENV PORT=8000
USER nobody
```

`VIPS_VECTOR` is the thread pool size handed to libvips, and `MALLOC_ARENA_MAX=2` plus jemalloc is a considered choice about allocator behaviour under many concurrent requests. Running as `nobody` in the final stage is also the right default. The practical takeaway is that imagor is not a pure-Go program you can cross-compile anywhere; it is CGO, it needs libvips, and the Docker image is the supported path unless you build the base image yourself.

## Filters, animated formats and watermarks in one chain

The filter vocabulary is wide enough that the sample URLs read like a demonstration. In the README you can see smart cropping, format and quality control, colour manipulation and watermark composition:

```text
http://localhost:8000/unsafe/fit-in/200x150/filters:fill(yellow):watermark(raw.githubusercontent.com/cshum/imagor/master/testdata/gopher-front.png,repeat,bottom,0,40,40)/raw.githubusercontent.com/cshum/imagor/master/testdata/dancing-banana.gif
```

That single path resizes with fit-in, fills the padding with yellow, and composites a watermark image nine times at the bottom right. Doing that with three separate calls would mean three fetches and three encodes; here it is one request, which is the point of the server rather than a convenience.

The release history shows the filter surface still growing. v1.9.6 added arbitrary rotate angle to the vips filter and fixed a data race on singleflight and fanout, and v1.9.5 added a max-age filter and an invert filter. The singleflight fix is the interesting one for anyone running several imagor instances, since a data race in the fanout path is the kind of bug that shows up as intermittent corruption under load rather than as a crash. The test images in `testdata/` include animated GIFs, and the repository topics list avif, jpeg-xl, gif, png, jpeg and webp, so animated input is a first-class case rather than an accident.

## Loaders, result storage and the files that make it a library too

The repository tree is organised around pluggable boundaries rather than around HTTP handlers. `loader/` holds the HTTP and file loaders, `storage/` holds result storage, `processor/` the libvips pipeline, `imagorpath/` the URL parser, `fanoutreader/` the concurrent multi-source reader, `metrics/` Prometheus instrumentation, and `server/` the HTTP layer. `blob.go`, `context.go`, `option.go`, `errors.go` and `imagor.go` at the root are the embedding surface, which is how the same code works as a library.

The `examples/` directory makes that explicit: `examples/server/`, `examples/from_buffer/`, `examples/from_file/`, `examples/from_go_image/` and `examples/io_reader_writer/`. So there are two genuinely different ways to use this project. You can run the server and point a CDN or your application at it, or you can import the processor and resize an image inside a Go program without any HTTP involved. The README's Security and Storage pages, plus the loaders note in the consulting section, are where the multi-tenant story lives.

Storage support is visible in `go.mod`, which requires `github.com/aws/aws-sdk-go-v2/service/s3`, `cloud.google.com/go/storage` and `google.golang.org/api` directly rather than behind an interface package. So S3 and Google Cloud Storage are supported backends for cached results. Prometheus and Sentry are also first-class dependencies, alongside `github.com/rs/cors` and `github.com/peterbourgon/ff/v3` for flag parsing. The observability and deployment story is more complete than most projects of this size.

## Building locally and the three image variants the Makefile keeps

The `Makefile` is short and readable, and it encodes three different builds you would otherwise assemble by hand:

```text
build:
	CGO_CFLAGS_ALLOW=-Xpreprocessor go build -o bin/imagor ./cmd/imagor/main.go

dev: build
	./bin/imagor -debug -imagor-unsafe -upload-loader-enable
```

`docker-dev`, `docker-magick` and `docker-mozjpeg` each build a variant image. The `magick` variant sets `ENABLE_MAGICK=true`, which the `Dockerfile` uses to install ImageMagick into the runtime stage, and the `mozjpeg` variant passes `-vips-mozjpeg` so JPEG encoding goes through mozjpeg instead of libvips' built-in encoder. That is a real production decision: mozjpeg produces smaller files at comparable quality, and the option to switch encoders without forking is worth more than the default.

`-upload-loader-enable` in the `dev` target points at a feature the security story depends on, since an upload loader changes the trust boundary compared with fetching remote URLs. `reset-golden` in the same file is a hint about how the project tests image output: golden files under `testdata/golden` are compared against generated output, so a libvips upgrade or an encoder change shows up as a diff you have to accept deliberately rather than as silent pixel drift.

## Where the README stops and docs.imagor.net takes over

The README is a good front door and a short one. It gives you the container command, sample URLs, three original images to point at, and five links: Image Endpoint, Filters, Storage, Security and Configuration. Everything that matters for a production decision sits one hop behind those links, including the benchmark pages the README cites when it claims imagor is one of the fastest image processing servers.

The release record is short and recent, which is a good sign about maintenance: v1.9.6 published on 2026-08-25, v1.9.5 on 2026-08-20 and v1.9.4 on 2026-08-10, all three with substantive fixes rather than version bumps. v1.9.4 in particular blocked embedded IPv4 transition addresses and unspecified dial targets in the httploader, which is the sort of change that tells you the security model is being taken seriously by the maintainer. The last push to the repository was on 2026-09-22, so this is an actively developed line with roughly 4,000 stars and only two open issues.

Two things are worth keeping in view as you read the docs. First, `imagorvideo` is a separate project that adds ffmpeg C bindings for video thumbnails, so video is not in this repository. Second, commercial consulting is offered for production architecture, custom components, multi-tenant setups and migration planning, which is unusual for a project at this size and tells you something honest about who uses it.

## Conclusion

imagor is at its best when you already have a thumbor URL in an existing frontend and want the same paths served by something faster and cheaper to run, or when you want image processing as a Go library rather than a sidecar. The repository is convincing on the streaming and memory story, because libvips and the vipsgen binding are doing real work there, and the Docker image is a single command with jemalloc and libvips already wired up. What the repository does not settle is filter throughput on your own traffic, S3 and GCS storage behaviour, and the multi-tenant loader patterns, all of which live on docs.imagor.net. Start with the `-imagor-unsafe` container against the gopher.png test image, then read the security page before the flag ever reaches a shared environment.

## FAQ

### What is imagor and what does it actually do with an image request?

imagor is an image processing server and Go library built on libvips. It reads a thumbor-style URL, where dimensions and filters are encoded in the path, fetches or reads the source image, runs the filter chain through libvips, and returns the processed image. It supports resizing, smart cropping, format and quality changes, colour filters and watermark composition in a single request.

### Is imagor a drop-in replacement for thumbor?

The README describes it as a high-performance drop-in replacement that adopts the thumbor URL syntax, so existing thumbor paths largely carry over. In practice the paths that map directly are the ones you would expect: unsafe URLs, fit-in, dimensions, smart crop and filters. Behaviour that depends on thumbor's own default filter order or on loaders imagor does not implement is worth checking against the Image Endpoint and Filters pages on docs.imagor.net before migrating a large URL set.

### What do I need to run imagor outside Docker?

libvips and the Go bindings. The project is CGO based, with the binding `github.com/cshum/vipsgen`, and the official images build on `ghcr.io/cshum/imagor-base:vips8.18.5-r14`, which ships libvips already compiled. Without that base image you need to install libvips development headers yourself and make sure pkg-config can find them, which is why the Dockerfile sets PKG_CONFIG_PATH to /opt/imagor/lib/pkgconfig.

### How does imagor store processed results?

Through the storage layer in the repository's `storage/` directory, with S3 and Google Cloud Storage supported directly, since the AWS SDK v2 S3 client and cloud.google.com/go/storage are required modules in go.mod. Where to configure the bucket, the key layout and how result invalidation works is documented on the Storage page rather than in the README.

## Sources

- [cshum/imagor on GitHub](https://github.com/cshum/imagor)
- [License: Apache-2.0](https://github.com/cshum/imagor/blob/master/LICENSE)
- [Project website](https://imagor.net)
- [README](https://github.com/cshum/imagor/blob/master/README.md)
- [Releases](https://github.com/cshum/imagor/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cshum-imagor
