Automatically convert JPEG and PNG images to WebP or AVIF on the fly using the image optimization agent. The agent negotiates the best format from the client’s Accept header, caches converted images on disk, and falls back to the original on any error.
Use Case
- Reduce image payload size by 30-80% with modern formats
- Serve WebP to Chrome/Firefox/Safari and AVIF where supported
- Cache converted images to avoid repeated CPU work
- No changes needed to origin servers or image pipelines
- Graceful fallback — the agent never makes a response worse
Architecture
┌─────────────────┐
│ Clients │
│ Accept: webp │
└────────┬────────┘
│
┌────────▼────────┐
│ Zentinel │
│ │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌──────────┐
│ Image │ │ Static │ │ API │
│ Origin │ │ Assets │ │ Backend │
└─────────┘ └─────────┘ └──────────┘
│
┌────────▼────────┐
│ Image Opt │
│ Agent │
│ ┌───────────┐ │
│ │ FS Cache │ │
│ └───────────┘ │
└─────────────────┘
Prerequisites
Install the image optimization agent:
# Via bundle (recommended)
zentinel bundle install image-optimization
# Or via cargo
cargo install zentinel-agent-image-optimization
Configuration
Create zentinel.kdl:
// Image Optimization Example
// Converts JPEG/PNG responses to WebP/AVIF with caching
system {
workers 4
}
listeners {
listener "http" {
address "0.0.0.0:8080"
}
}
agents {
agent "image-opt" type="custom" {
unix-socket "/tmp/image-optimization.sock"
events "request_headers" "response_headers" "response_body" "request_complete"
timeout-ms 5000
failure-mode "open"
config {
"formats" ["webp", "avif"]
"quality" { "webp" 80; "avif" 70 }
"max_input_size_bytes" 10485760
"max_pixel_count" 25000000
"eligible_content_types" ["image/jpeg", "image/png"]
"passthrough_patterns" ["\\.gif$", "\\.svg$", "\\.ico$"]
"cache" {
"enabled" true
"directory" "/var/cache/zentinel/image-optimization"
"max_size_bytes" 1073741824
"ttl_secs" 86400
}
}
}
}
upstreams {
upstream "origin" {
target "127.0.0.1:9000"
}
}
routes {
route "images" {
matches { path-prefix "/" }
upstream "origin"
agents "image-opt"
}
}
Running
Start the agent and proxy:
# Terminal 1: Start the image optimization agent
zentinel-image-optimization-agent \
--socket /tmp/image-optimization.sock \
--log-level info
# Terminal 2: Start Zentinel
zentinel -c zentinel.kdl
Testing
# Request a JPEG — should receive WebP if your client supports it
curl -H "Accept: image/webp,image/*" \
-o optimized.webp \
-D - \
http://localhost:8080/photo.jpg
# Check response headers
# Content-Type: image/webp
# X-Image-Optimized: webp
# X-Image-Original-Size: 245760
# Vary: Accept
# Request again — should be a cache hit
curl -H "Accept: image/webp,image/*" \
-o cached.webp \
-D - \
http://localhost:8080/photo.jpg
# X-Image-Optimized: cache-hit
# Request without WebP support — original passes through
curl -H "Accept: image/jpeg" \
-o original.jpg \
-D - \
http://localhost:8080/photo.jpg
# Content-Type: image/jpeg (unchanged)
# Request AVIF
curl -H "Accept: image/avif,image/webp,image/*" \
-o optimized.avif \
-D - \
http://localhost:8080/photo.jpg
# Content-Type: image/avif
# X-Image-Optimized: avif
Customization
WebP Only (Fastest)
If you don’t need AVIF support:
config {
"formats" ["webp"]
"quality" { "webp" 85 }
}
Skip Large Images
Lower the size limits to avoid spending CPU on hero images:
config {
"max_input_size_bytes" 5242880
"max_pixel_count" 10000000
}
Exclude Specific Paths
Skip images that are already optimized or served from a CDN:
config {
"passthrough_patterns" ["\\.gif$", "\\.svg$", "/cdn/", "/thumbnails/"]
}
gRPC Transport
For higher throughput or remote deployment:
agent "image-opt" type="custom" {
grpc "http://image-opt-service:50060"
events "request_headers" "response_headers" "response_body" "request_complete"
timeout-ms 5000
failure-mode "open"
}
zentinel-image-optimization-agent --grpc 0.0.0.0:50060
Response Headers
| Header | Example | Description |
|---|---|---|
Content-Type | image/webp | Converted format |
X-Image-Optimized | webp, avif, cache-hit | Conversion result |
X-Image-Original-Size | 245760 | Original size in bytes |
Vary | Accept | Downstream caches vary by format |
Related Examples
- Static Site — Serve and optimize static assets
- HTTP Caching — Combine with HTTP response caching
- Load Balancer — Optimize images across multiple origins