Image Pipeline
Vylux handles images on a synchronous request path optimized for low latency and high cache hit rates.
Endpoints
/img/:sig/:opts/:sourcewithIMAGE_SOURCE_KEY, otherwise/img/:sig/:opts/*: transform and return an image immediately/original/:sig/:sourcewithIMAGE_SOURCE_KEY, otherwise/original/:sig/*: proxy the original object/thumb/:sig/*: proxy generated image assets
/img core flow
- parse the output format and source segment from the request path
- parse
opts, currently supportingw,h, andq - validate the HMAC signature
- decrypt and authenticate the source token when
IMAGE_SOURCE_KEYis enabled - check the memory LRU
- check the media-bucket storage cache
- on a miss, fetch the original from the source bucket
- transform the image with libvips
- write the result into the memory LRU synchronously and the storage cache asynchronously
- return the image bytes with CDN-friendly headers
Signing canonicalization
In plaintext mode, Vylux does not sign the raw browser URL string directly. It first canonicalizes:
optsintow -> h -> qorder- the encoded source path into a decoded object key
jpegintojpg
This prevents logically equivalent requests from producing different signatures or cache keys.
In encrypted mode, Vylux signs the canonical options, opaque source token, and canonical format in the vylux/image-url/v1 domain. It verifies that signature before attempting AES-GCM decryption. Different tokens for the same source key still resolve to the same processing cache key after decryption.
Cache layers
| Layer | Role | Write timing |
|---|---|---|
| memory LRU | hot results inside one process | synchronous |
| media-bucket storage cache | shared derived-image cache across processes and pods | asynchronous |
| CDN | public distribution layer | driven by response headers |
Current response behavior includes:
Cache-Control: public, max-age=31536000, immutableETagbased on the returned bytesVary: Accept
Cache path
Real-time image results are written to:
cache/{processing_hash}.{format}
processing_hash is derived from the source key and transformation parameters, not from the upstream job hash field.
singleflight and concurrency
The synchronous image path uses two singleflight groups:
sourceFlight: suppress duplicate fetches of the same original imageprocessFlight: suppress duplicate transforms for the same source-plus-options combination
This matters when a cold cache receives a burst of identical requests.
/original and /thumb
These two endpoints do not transform content:
/originalproxies objects from the source bucket/thumbproxies already-generated image assets from the media bucket
Both require HMAC-signed URLs, but /thumb uses a dedicated thumb signing domain so signatures cannot be reused across /thumb and /original.
How image:thumbnail differs
Although both features deal with images, they serve different purposes:
| Path | Mode | Output location | Typical use |
|---|---|---|---|
/img | synchronous | cache/{processing_hash}.{format} | browser-facing dynamic image delivery |
image:thumbnail | asynchronous | images/{prefix}/{hash}/{variant}.{format} | stable pre-generated variants |
image:thumbnail does not route through /img. The worker generates named variants directly.
Error semantics
The image path is intentionally strict:
400: invalid parameters or unsupported format403: invalid signature404: missing source object422: source exists but cannot be processed502: temporary source-storage failure500: unexpected internal error
There is no pretend-success fallback image. Failures remain visible to upstream callers and observability tooling.