Mediator is a small, standalone service for efficient image processing. Images are processed on the fly with libvips
and cached by CDN. The service is intended as a replacement for all common image operations in your web apps.
You can use it to:
- Resize images. Generate thumbnails and responsive images on the fly.
- Crop images. Crop images (currently only smart crop is supported).
- Apply effects. Apply filters to images (currently only
pixelate
is supported). - Proxy media files: proxy images and other files using secure, signed URLs.
- Strip metadata. Remove metadata from images to reduce file size and protect user privacy.
See also: example code
- Fast. Written in Go and uses
libvips
for image processing. - Simple. A small web service with minimal configuration.
- Secure. Supports signed URLs and authentication tokens.
- Easy to integrate. Works with any CDN and storage provider.
- Easy to deploy. Just use the provided Docker image.
- Most common image operations. Resize, crop, apply effects, and more.
- Auto WebP. Auto-convert images to WebP and serve them to browsers that support it.
- PDF preview. Generate preview images for PDF files.
- Limited features. Mediator aims to be cover the most common use cases. If you need more advanced features, consider a) contributing to the project, or b) using an alternative service.
- New software. Although Mediator had been running in production without issues, it's still a new project, shaped by limited use cases.
Mediator is distributed as a docker image:
docker run --rm -it --env-file .env --publish 8000:8000 ghcr.io/pch/mediator:latest
Mediator does not offer a built-in cache and will re-download images on each request. The service is intended to run behind a CDN, which will cache the processed images.
To transform an image, use the /image/transform/:source/:path
endpoint. The :source
parameter is the name of the source, and :path
is the path to the image file (e.g. S3 key/path).
The /image/transform
endpoint accepts the following query parameters:
Parameter | Description |
---|---|
op |
Operation names, separated by commas. Supported operations: fit , smartcrop , pixelate . Default: fit |
w |
Width of the target image. |
h |
Height of the target image. |
format |
Output format. Supported values: jpeg , png , gif , webp , auto . Defaults to Content-Type of the requested image. Set auto to return WebP to browsers that support it. |
strip |
Strip metadata from the image. Supported values: true , false . Default: true |
q |
Quality of the output image. Supported values: 0-100 . Default: 80 |
pixelatefactor |
Pixelate factor, for example: 1-100 . The smaller the number, the less "pixelized" the result will be. Default: 20 |
page |
Page number, used for PDF previews and for GIF previews (the number of the frame to extract). Default: 1 |
s |
Signature. Required when MEDIATOR_SECRET_KEY is set. |
Currently, the following operations are supported:
- Fit. Resize the image to fit within the specified dimensions, keeping the aspect ratio. The image will be downsized to the largest size that fits within the specified dimensions.
- Smartcrop. Crop the image to the specified dimensions, using a smart algorithm to find the most interesting part of the image.
- Pixelate. Pixelate the image. The
pixelatefactor
parameter controls the level of pixelation.
To proxy a file from a private source, use the /proxy/:source/:path
endpoint. The :source
parameter is the name of the source URL. The :path
and the query string are passed to the source URL.
Use case: this was originally built to proxy screenshots and PDFs generated by a private service, running puppeteer. Mediator forwards the requests to the service, taking care of the URL signing (to prevent spamming the service with automated requests) and caching (via CDN).
NOTE: Cache-Control
is not set for proxied files. The source server should take care of the caching policy.
The signing mechanism is based on the MEDIATOR_SECRET_KEY
environment variable. When the key is set, the service will require a s
parameter in the query string, containing the signature of the request.
The algorithm to generate the signature:
path_with_query_params = "/image/transform/images-dev/2024/02/2431ckhy8z9s1tya4trk21pb3f.jpg?h=300&w=300"
signature = HMAC-SHA256(MEDIATOR_SECRET_KEY, path_with_query_params)
signed_url = path_with_query_params + "&s=" + signature
See also: examples
Mediator can be configured using ENV
variables:
Variable | Description | Default |
---|---|---|
MEDIATOR_SOURCES |
Required. List of supported sources to pull images from. A semicolon-separated list of key=values . Example:mybucket=https://mybucket.s3.amazonaws.com;mybucket-dev=https://mybucket-dev.s3.amazonaws.com |
— |
MEDIATOR_SECRET_KEY |
Optional, but highly encouraged. Secret random key, used to generate URL signatures. Can be ganerated with:dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d '=' |
"" |
MEDIATOR_AUTH_TOKEN |
Optional. Token for authenticating image requests (for Authorization: Bearer <AUTH_TOKEN> ). You can set it when configuring your CDN to prevent direct access to the service. |
"" |
MEDIATOR_PATH_PREFIX |
Optional. Prefix for the image processing URL paths. Useful when you have a CDN pulling from multiple sources. Example: PATH_PREFIX=/my-prefix will change the transform URL to /my-prefix/image/transform/:source/:path . |
"" |
MEDIATOR_CACHE_CONTROL |
Value for the Cache-Control header. |
public, max-age=31536000 |
MEDIATOR_DOWNLOAD_MAX_SIZE |
File size download limit, in bytes. | 50MB |
MEDIATOR_DOWNLOAD_TIMEOUT |
Download timeout, in seconds. | 10s |
MEDIATOR_HTTP_PORT |
HTTP port for the service. | 8000 |
MEDIATOR_LOG_LEVEL |
Log level. Supported values: debug , info , warn , error . |
info |
Example configuration for Kamal, with Mediator running as an accessory:
service: example
image: pch/example # your main app
servers:
web:
hosts:
- 123.123.123.123
labels:
traefik.http.routers.peeper.entrypoints: websecure
traefik.http.routers.peeper.rule: Host(`example.com`)
traefik.http.routers.peeper.tls.certresolver: letsencrypt
accessories:
mediator:
image: ghcr.io/pch/mediator:latest
roles:
- web # will run on the same host as the main app
port: "8000:8000"
env:
clear:
MEDIATOR_SOURCES: images-dev=https://example-dev.s3.amazonaws.com;images=https://example-prod.s3.amazonaws.com
secret:
- MEDIATOR_SECRET_KEY
- MEDIATOR_AUTH_TOKEN
labels:
traefik.http.routers.mediator.entrypoints: websecure
traefik.http.routers.mediator.rule: Host(`mediator.example.com`)
traefik.http.routers.mediator.tls.certresolver: letsencrypt
traefik.tcp.services.mediator.loadbalancer.server.port: 8000
If you decide to use CloudFront for CDN, there are only a few considerations to take into account:
- When setting up an origin, make sure to set the
Authorization
header toBearer <YOUR_AUTH_TOKEN>
to prevent direct (non-cached) access to the service. - In the Behavior settings, you have to make sure that the query string is forwarded
- In the "Choose which headers to include in the cache key" part, add the
Accept
header if you want to serve WebP images to browsers that support it.