Skip to content

iiiris

pipeline status coverage report latest release license IIIF Image API container

A IIIF Image API 3.0 (and 2.x) compatible image server in Go, backed by libvips for image processing.

Status: functional, pre-1.0. Full IIIF Image API 3.0 and 2.0 Level 2 surface works end-to-end against the libvips pipeline; comprehensive caching, three source backends, scriptable hooks, an operator admin UI, and a multi-arch container release pipeline ship in the box. See docs/ for feature reference and CHANGELOG.md for what's landed.

Vision

Our ambition is broad: to cover the full breadth of the IIIF APIs in a single, performant, easy-to-operate stack. The Image API is our grounding point — the foundation we're building first and building well — and the rest of the IIIF surface orients around it.

Two principles guide the work:

  • Performance-competitive. A first-class IIIF server should be fast and cheap to run. We benchmark iiiris head-to-head against established servers and treat parity-or-better on throughput and latency as a release gate, not an afterthought.
  • Highly feature-complete. We aim to cover each API's surface honestly — no advertised feature that the server won't actually serve.

Roadmap. With the Image API as the load-bearing core, we intend to grow iiiris outward across the full breadth of the IIIF APIs — Presentation, Content Search, Change Discovery, and the broader ecosystem — so a single performant Go binary can stand behind a complete IIIF deployment. See docs/ROADMAP.md for the phased plan and the road to a platform 1.0.

Requirements

  • Go 1.25+
  • libvips (with OpenJPEG for JP2 support):
  • macOS: brew install vips
  • Debian/Ubuntu: apt-get install libvips-dev libopenjp2-7-dev pkg-config

Quick start (binary)

go mod tidy
make build           # → ./bin/iiirisd (use make build or `go build -o bin/...`;
                     #   bare `go build ./cmd/iiirisd` drops the binary in the repo root)
./bin/iiirisd

Then:

curl http://localhost:8080/health
curl http://localhost:8080/iiif/3/sample.jpg/info.json
curl -o sample.jpg http://localhost:8080/iiif/3/sample.jpg/full/max/0/default.jpg

For the admin UI, set credentials and visit http://localhost:8080/admin/:

IIIRIS_ADMIN_USER=admin IIIRIS_ADMIN_PASS=test ./bin/iiirisd

Quick start (docker compose)

cp .env.example .env
# edit .env: set a real IIIRIS_ADMIN_PASS
docker compose up --build

The compose stack bind-mounts ./examples/images as the IIIF source root — a small corpus laid out to demo both APIs. Once it's up:

# Image API
curl localhost:8080/iiif/3/thumbnail.jp2/info.json
curl -o page.jpg localhost:8080/iiif/3/library/manuscript/page-001.jp2/full/400,/0/default.jpg

# Presentation API (auto-derived from the mounted directory)
curl localhost:8080/iiif/presentation/3/library              # Collection
curl localhost:8080/iiif/presentation/3/library/manuscript   # Manifest (sidecar labels)

# Bundled viewer — open in a browser
open http://localhost:8080/view/library/manuscript           # Mirador (default, self-hosted)
open http://localhost:8080/view/library/manuscript?viewer=uv  # Universal Viewer (from CDN)

Override IIIRIS_LOCAL_IMAGES in .env to serve your own directory (any folder of images works; a folder-of-folders becomes a collection). To demo Redis-backed caching, add the overlay:

docker compose -f docker-compose.yml -f docker-compose.redis.yml up --build

See docs/deployment.md and examples/images/README.md for details.

Feature highlights

  • IIIF Image API 3.0 (Level 2) and 2.x with proper grammar per-version (multi-segment identifiers, full/max size, ^ upscaling, native quality, etc.). info.json advertises tiles, maxWidth/maxHeight/maxArea honestly — the same numbers the pipeline enforces.
  • IIIF Presentation API 3.0 (and 2.1): serve stored manifests and collections, or auto-derive them from a directory of images — one canvas per image, each painted by the server's own Image API service, sized from a header-only probe. Filesystem conventions are overridable by a manifest.{yaml,json} sidecar or a hook. A bundled viewer (self-hosted Mirador by default, Universal Viewer via ?viewer=uv) at /view/{id} makes a deployment usable end to end: point iiiris at a folder, get a manifest and a viewer. Stateless and on by default. See docs/presentation.md.
  • IIIF Content State API 1.0 (stateless core): shareable "open the viewer exactly here" links. The bundled viewers read the iiif-content parameter — /view/?iiif-content=<state> opens Mirador or UV at a Manifest, Canvas, or Canvas region — and /content-state/mint produces such a state (and its /view URL) from one of your Presentation identifiers. Stateless and on by default. See docs/content-state.md.
  • Model Context Protocol (MCP) server (read-only, off by default): expose iiiris to AI agents over a /mcp Streamable HTTP endpoint — browse a collection, get_image_info, and get_region (rendered as an image the agent sees). Any MCP host connects by URL. See docs/mcp.md.
  • libvips image pipeline via govips: full region / size / rotation / quality (incl. true bitonal via threshold-by-clipping) / format coverage. JPEG, PNG, TIFF, WebP for both decode and encode; JP2 / HTJ2K (decode) via OpenJPEG ≥ 2.5.0, accepting both boxed .jp2/.jph files and raw J2K codestreams (the default output of OpenJPH's ojph_compress).
  • Metadata privacy by default: derivatives strip embedded EXIF / XMP / IPTC (camera, serial, GPS, captions), normalize colour to sRGB, and bake EXIF orientation into the pixels — so sensitive source metadata never ships and images render upright with correct colour in any viewer. image.metadata: preserve opts back into passthrough.
  • 3 cache slots × 4 backends: RenderCache, InfoCache, OriginCache each on heap (in-memory size-LRU), filesystem (on-disk size-LRU, concurrent-process safe; multi-replica via a Redis coordinator), s3 (multipart streaming, lifecycle-policy eviction), or redis (MAXMEMORY + per-entry TTL) — or none to switch a slot off.
  • 3 source backends: filesystem, HTTP, S3 (with directory listing). HTTP/S3 fronted automatically by an OriginCache. All three implement per-source health checks.
  • HTTP cache semantics: Cache-Control, ETag, conditional GET → 304 (via http.ServeContent). Access-Control-Allow-Origin: * on every IIIF response; Accept: application/ld+json content negotiation on info.json.
  • Hook scripting: noop / lookup (text/template) / js (sandboxed JavaScript, pure-Go goja) / webhook (HTTP POST). Translate IIIF identifiers into arbitrary backend keys, gate access on request headers, and override Presentation descriptors per-request. (lua is still available but deprecated — removed in 1.0.)
  • Plugin-driven overlays / watermarking: a hook can composite an image overlay onto image responses — a nine-position anchor grid with offsets, shrink/scale/tile fit and opacity, either at a fixed output size or scaled with the source. Folded into the render-cache key and fails closed.
  • IIIF Authorization Flow API 2.0 with IIIF Authentication 1.0 dual-advertisement for legacy-viewer compatibility: all four 2.0 services (probe / access / access-token / logout) at /iiif/auth/, the smaller 1.0 surface (login / token / logout) at /iiif/auth/v1/, all four interaction patterns (clickthrough / active / kiosk / external), four access-service backends (builtin with bcrypt/apr1 htpasswd + custom template override / reverse-proxy header / signed-callback external / first-class oidc). Per-rule profile selection, denied-view substitute (size degrade or hook-supplied alternate identifier), tier-keyed render and info cache.
  • Operator admin UI under /admin/ (HTTP Basic auth): a live (Server-Sent Events) status dashboard with per-route metrics + p95 sparklines + per-cache stats + per-source health, a config editor (curated fields, some hot-applied without a restart), cache management (per-key purge + per-cache purge-all), file browser, OpenSeadragon zoomable viewer.

iiiris admin status dashboard
The live status dashboard — see docs/admin.md for the full operator UI tour.

  • Bounded resource use: configurable global + per-IP concurrency caps, source-area + output-area + output-dimension guards. Client-fault pipeline errors return 400.
  • Liveness /health + readiness /ready/ready runs every configured source's healthcheck in parallel; suitable as the Kubernetes readiness probe target.
  • Prometheus metrics (opt-in): HTTP per-route counters and latency histograms, cache hit/miss/footprint per cache, source open latency, render-pipeline timings and queue wait, auth decisions, source health, build info. On the main listener or a dedicated port, optional Basic auth. See docs/deployment.md "Prometheus metrics".
  • Error tracking via Sentry (opt-in): reports recovered panics (with stack traces), 5xx responses, and internal source/decode/cache errors to a Sentry project, tagged by environment, build release, and route. Privacy-first — client IPs, cookies, auth headers, and query strings are stripped by default. Set sentry.dsn to enable. See docs/configuration.md.
  • Distributed tracing via OpenTelemetry (opt-in): per-request root spans with child spans across source open and cache get/put, exported over OTLP to any collector (Tempo/Jaeger/…) or to Sentry's OTLP endpoint; inbound W3C traceparent is honored. Sentry errors link to their trace. Set tracing.enabled + tracing.otlp_endpoint. See docs/configuration.md.
  • Zero-config startup: ./bin/iiirisd works with no flags, no file, no env vars — serves bundled testdata/sample.jpg on :8080.
  • Distribution: tagged releases produce a distroless multi-arch container image (linux/amd64 + linux/arm64; ~46 MB, no shell) at :vX.Y.Z / :latest, a debian-slim variant at :vX.Y.Z-debian for operators who need shell tooling, and a linux/amd64 binary — all attached to each GitLab Release. docker-compose.yml ships a one-command local stack.
  • Windows support is a work in progress. A complete local Windows build path exists (MSYS2 UCRT64 + libvips; see docs/deployment.md) and produces a working iiirisd.exe, a runtime-DLL-bundled zip, and a WiX-v5 MSI installer with optional Windows-service registration + Event Log integration. Pre-built Windows artefacts are not currently attached to GitLab Releases — the automated CI pipeline that publishes them is still being landed.

Make targets

make build           # build ./bin/iiirisd (with VERSION stamp from git describe)
make test            # go test -race ./...
make bench           # go test -bench=. ./...
make run             # go run ./cmd/iiirisd
make lint            # golangci-lint run
make docker          # build deploy/Dockerfile
make release-binary  # stripped, version-stamped, local-platform binary
make tidy            # go mod tidy

The comparative iiiris-vs-Cantaloupe benchmark is not a make target — run it via bench/run.sh (local or AWS).

License

iiiris is licensed under the Apache License 2.0. See NOTICE for required attributions and THIRD_PARTY_LICENSES.md for the full dependency inventory — every bundled or linked component is permissively licensed (MIT / BSD / Apache-2.0); the runtime dependency on libvips is LGPL-2.1-or-later and compatible with Apache-2.0 outbound licensing.