Dynamic Image Optimization with imgproxy on STACKIT Cloud Foundry

Andreas Lehr, 12 December 2022

Images make up a vital part of the web and provide the foundation for superior user experiences on the Internet.
In fact, they are the most popular resource type and account for about 42% of the Largest Contentful Paint (LCP) element for websites. Therefore, optimizing your images should be one of the first priorities of tackling web performance issues.

But not only optimizing delivery is important, it’s also critical that Developers can rely on a simple and flexible solution for scaling, shrinking, cropping or watermarking images. This is where the open-source project imgproxy can play its strengths with its diverse set of on-the-fly image modifications.

Legacy solutions

Over the last century, there have been many solutions delivering content as it was uploaded or placed into htdocs folders. By using CMS editors it was possible to upload a 12 MB original image which was then delivered with 12 MB. Multiple images per page could result in long or never fully loading web pages.
The solution then was to limit upload sizes or resolutions for images, which then resulted in huge variations in image quality. And so both issues, quality variations and low speed downloads or large images, resulted in a poor user experience.

To encounter the quality issues, the uploading process was changed to be an asynchronous one. After uploading a full size image, a batch job goes through the queue and generates a predefined set of different image sizes and resolutions. This way the optimal resolution for a set of images was available, e.g. a proper sized thumbnail, search-result image and product-detail image for several screen sizes.
But on the other hand, this process of image processing is very CPU intense. Furthermore, lots of images had to be prerendered without ever being accessed. In addition, the storage space increased, as well as overall maintenance and backing up all the image variants.
For the CMS editor, the experience was suboptimal, as uploaded images weren’t live immediately. Overall this was not a sustainable practice and the user experience wasn’t a good one either.
Let’s quickly sum this challenges up:

  1. Image file sizes are often too large for web delivery
  2. Variations in image quality depending on upload restrictions
  3. Asynchronous process has downsides as well (CPU-intense, asynchronous, maintenance effort)

Dynamic image processing with imgproxy

Imgproxy is a good solution for all the challenges mentioned above and more.
The application can modify and optimize images on the fly and serve as a dynamic image modification engine in front of your original full size images - controlled via simple URL request.

It’s an open-source product with paid features available. The open-source version itself comes packed with 55+ features, including all security-related configuration options. Imgproxy prevents Image Bombs and provides DoS protection via a signature mechanism.
The source images URLs are signed with a key and a salt, or can be protected with an HTTP authorization header. This way, an attacker isn’t able to perform a DoS attack by requesting multiple image manipulations at once - see signing the URL for more details.

If you’re just investigating use-cases and capabilities with imgproxy - you can check the official website and play with the NASA Lander imgproxy demo directly on the top of the page.

Caching

Imgproxy does not include a caching layer - this one needs to be deployed by yourself. Each request coming to imgproxy will consume the same amount of CPU.
For small use-cases, it might be sufficient to use imgproxy without cache mechanism. Further, it highly depends on the traffic scenario, and it makes sense to cache image processing results. Either your Content Delivery Network (CDN) or a simple nginx configuration could be used to serve as a reverse proxy in order to cache all the images with proper HTTP-headers.

Image Formats: AVIF/WebP

Imgproxy does support a huge variety of source image formats.
In particular, imgproxy is capable of detecting the support of AVIF/WebP image formats in your browser. If the detection feature is enabled, imgproxy is able to deliver AVIF/WebP optimized versions of the requested image. Usually, WebP and AVIF images are delivered with a smaller file size than the original JPG.
Nowadays, WebP is still called a “modern image format” despite being released twelve years ago. It’s supported in Safari since September 2022.
AVIF was released in 2019 to supersede WebP - especially where bandwidth is priority.
You can check compatibility of all major browsers at canisue.com - here for WebP and over here for AVIF.

To give you an example regarding possible savings, here’s a comparison:
This shopping cart image is used over here on the homepage of our producing division: schwarz-produktion.com.

Type Format Kilobytes Bytes %
original JPEG 432 431.765 100
optimized JPEG JPEG 291 290.827 67,36
optimized WebP WebP 227 226.746 52,52
optimized AVIF AVIF 111 110.495 25,6

As you can see, the saving potential is pretty impressive - but hugely depends on the required image quality and traffic implications.

If you want to test your images with WebP or AVIF transformation you use, try the web-application over here at squoosh.app and play with the live preview.
For more details on modern image formats, like compression efficiency and image quality - you can check this article at smashingmagazine.com.

Autoscaling on STACKIT

First, a new project was created on STACKIT Portal including a Bucket from STACKIT S3 - the Object Storage service. The S3 Blob-storage serves as an upload destination for the original high-resolution images. Furthermore, a STACKIT Cloud Foundry (CF) instance was added to the project. Cloud Foundry provides the runtime environment to run the official container image of imgproxy as an application with multiple instances.

New applications in CF are created with the deployment and consist of 1-N application instances of the same container image. The application itself gets its configuration from environment variables predefinied during deployment - just as you know from docker already, e.g.:

1
2
3
4
5
6
7
- cf set-env $CF_APP IMGPROXY_ALLOWED_SOURCES "s3://"
- cf set-env $CF_APP IMGPROXY_ENFORCE_WEBP true
- cf set-env $CF_APP IMGPROXY_USE_S3 true
- cf set-env $CF_APP IMGPROXY_S3_ENDPOINT https://mydomain.schwarz
- cf set-env $CF_APP AWS_ACCESS_KEY_ID MyAccessKey
- cf set-env $CF_APP AWS_SECRET_ACCESS_KEY MySecretAccessKey
- cf set-env $CF_APP IMGPROXY_IGNORE_SSL_VERIFICATION true

During application creation, a load balancer with publicly reachable route is created for the 1-N application instances of the application. Finally, the publicly reachable route of the CF load balancer of the application instances is defined as origin in the CDN configuration.
The traffic chain looks like this:

Image processing requests originating from the end user reach the imgproxy through the CDN and the CF load balancer of the application. The imgproxy instance on CF is configured to scale up to a maximum of 100 application instances and scale down to a minimum of 5 instances. The so called “autoscaling manifest” is a simple JSON file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
	"instance_min_count": 5,
	"instance_max_count": 100,
	"scaling_rules": [
		{
			"metric_type": "cpu",
			"breach_duration_secs": 60,
			"threshold": 60,
			"operator": ">=",
			"cool_down_secs": 60,
			"adjustment": "+2"
		},
		{
			"metric_type": "cpu",
			"breach_duration_secs": 60,
			"threshold": 5,
			"operator": "<=",
			"cool_down_secs": 60,
			"adjustment": "-1"
		}
	]
}

As you can see, we’re using a pretty aggressive strategy for automatic upscaling, adding ten application instances if overall CPU usage is greater than 60% in 60s. This proactive strategy is necessary, as we suspect another batch of new and uncached flyers being uploaded to the application after seeing an initial CPU increase.
Down scaling happens in a more defensive way by removing one application instance if overall CPU usage is lower than 15% for 60 seconds.

Autoscaling happens all the time. We’re totally happy that this works completely hazzle free and without interference for the user.

Use-Case: Digital Leaflets for Lidl & Kaufland

The most popular use case for imgproxy at SCHWARZ this way is the Product “Digital Leaflets”. It’s currently used by all Lidl countries (30+) and in all Kaufland countries as well.

We’re storing around 2 TB of Source images and deliver 1 PB optimized images per month to customers all around the world through our CDN. On average, we deliver around 5 billion HTTP requests of imgproxy opzimized images per month. Out of these, only around 4 million requests, less than 1%, reach our origin imgproxy instances. We’re pretty happy with the high caching ratio.
You can check the Digital Leaflets over here on lidl.co.uk as an example. Another usecase are these Digital Kaufland leaflets in Romania.

Browsing a flyer, you can see that we’re loading a generic image of the leaflet as an overview. But clicking a product, your browser loads a larger variant of the same image as dynamic request through imgproxy. Check the Dev Tools of your browser to see the on-click reload.

Another feature of the digital leaflets is the possibility to save items on your wishlist (only available in e-commerce enabled countries). So, the performant delivery of images is vital to the success of the application and the overall customer experience.

Summary

As you can see, imgproxy is quite helpful in having a standard and flexible solution available for optimizing and modifying your images on the fly.
For further details details you can watch this YouTube recording of our SIT Meetup on web performance.
Thanks to the folks at imgproxy for providing such a nice and useful application open-source. Check out the imgproxy blog for news and updates on the product.