iiiris¶
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 andCHANGELOG.mdfor 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/maxsize,^upscaling,nativequality, etc.). info.json advertisestiles,maxWidth/maxHeight/maxAreahonestly — 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. Seedocs/presentation.md. - IIIF Content State API 1.0 (stateless core): shareable "open the viewer
exactly here" links. The bundled viewers read the
iiif-contentparameter —/view/?iiif-content=<state>opens Mirador or UV at a Manifest, Canvas, or Canvas region — and/content-state/mintproduces such a state (and its/viewURL) from one of your Presentation identifiers. Stateless and on by default. Seedocs/content-state.md. - Model Context Protocol (MCP) server (read-only, off by default): expose
iiiris to AI agents over a
/mcpStreamable HTTP endpoint —browsea collection,get_image_info, andget_region(rendered as an image the agent sees). Any MCP host connects by URL. Seedocs/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/.jphfiles and raw J2K codestreams (the default output of OpenJPH'sojph_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: preserveopts back into passthrough. - 3 cache slots × 4 backends:
RenderCache,InfoCache,OriginCacheeach onheap(in-memory size-LRU),filesystem(on-disk size-LRU, concurrent-process safe; multi-replica via a Redis coordinator),s3(multipart streaming, lifecycle-policy eviction), orredis(MAXMEMORY + per-entry TTL) — ornoneto 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 (viahttp.ServeContent).Access-Control-Allow-Origin: *on every IIIF response;Accept: application/ld+jsoncontent negotiation oninfo.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. (luais 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/tilefit 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 (builtinwith bcrypt/apr1 htpasswd + custom template override / reverse-proxyheader/ signed-callbackexternal/ first-classoidc). 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.
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—/readyruns 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.dsnto enable. Seedocs/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
traceparentis honored. Sentry errors link to their trace. Settracing.enabled+tracing.otlp_endpoint. Seedocs/configuration.md. - Zero-config startup:
./bin/iiirisdworks with no flags, no file, no env vars — serves bundledtestdata/sample.jpgon: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-debianfor operators who need shell tooling, and a linux/amd64 binary — all attached to each GitLab Release.docker-compose.ymlships 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 workingiiirisd.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).
Where to read next¶
docs/— operator and developer reference per subsystemdocs/ROADMAP.md— where iiiris is headed: the phased path to a IIIF-platform 1.0docs/architecture.md— package map and request flowdocs/configuration.md— YAML + env varsdocs/iiif-compliance.md— exact spec coveragedocs/presentation.md— IIIF Presentation API: serve, auto-derive, the bundled viewerdocs/content-state.md— IIIF Content State API: shareable view links, mint/resolvedocs/mcp.md— Model Context Protocol server: read-only IIIF tools for AI agentsdocs/iiif-auth.md— IIIF Authorization Flow 2.0 services, patterns, backendsdocs/deployment.md— building, releasing, docker composebench/— comparative performance benchmark vs CantaloupeCHANGELOG.md— what's shipped per releasedocs/specs/presentation.md— Presentation API contract (serve/derive/viewer invariants)docs/specs/scaffold.md— durable foundation: project priorities, package-boundary invariants, contractsdocs/specs/releases.md— release contract: versioning, artifact set, cut-release flowCLAUDE.md— working conventions for contributors
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.