################################################################################ # Ultra Image Server # A production grade image processing server setup powered by `imgproxy` and nginx # # Author: Mai Nhut Tan # Copyright: 2021-2024 SHIN Company https://code.shin.company/ # URL: https://shinsenter.github.io/docker-imgproxy/ ################################################################################ # ======================================================================== # # Block access from some bad IPs # See: http://nginx.org/en/docs/http/ngx_http_access_module.html#deny # Add your bad client IPs here. E.g. deny 134.119.219.93; # ======================================================================== # # This upstream links to `imgproxy` container outside. # Do not change this upstream # unless you are going to change `imgproxy`'s container name. upstream upstream_imgproxy { server imgproxy:8080; keepalive 16; } # ======================================================================== # # Cache zone # See: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path # # Let's assume average file size is ~20kb # max_size: 1mb of keys_zone can store 7,000 keys * 20kb = ~140mb # 4mb of keys_zone can store ~28,000 keys = ~560mb # 32mb of keys_zone can store ~224,000 keys = ~4.8gb # proxy_cache_path /var/cache/nginx/imgproxy levels=1:2 use_temp_path=off keys_zone=IMAGE_CACHE:32m max_size=5G min_free=32m inactive=7d; # ======================================================================== # # Temporary variables ##! **`$uri_omitted_origin`** ## Get the URI after omitting origin server. ## This setup assumes that an origin server starts with an `@` symbol. map $uri $uri_omitted_origin { default '$uri'; ~^(/@[^/]+)?(?/.+)$ '$parsed_path'; } ##! **`$uri_omitted_origin_preset`** ## Get the URI after omitting origin server and preset name. ## This setup assumes that preset name starts with an underscore (`_`) symbol. map $uri_omitted_origin $uri_omitted_origin_preset { default '$uri'; ~*^(/_[a-z0-9_-]+)?(?/.+)$ '$parsed_path'; } # ======================================================================== # # Variables for `imgproxy` ## **`$use_imgproxy`** ## This flag indicates that the request will be proceeded by `imgproxy`. map $uri $use_imgproxy { default 0; # Add any rules that you want to skip image processing. #> E.g. this line excludes files under "hq-cactus" folder. ~^/hq-cactus/ 0; # Else, process all image files with these file extensions ~*\.(jpe?g|png|gif|tiff?|bmp)$ 1; } ##! **`$imgproxy_cache`** ## Default cache zone for `imgproxy`. map $use_imgproxy $imgproxy_cache { default off; 1 IMAGE_CACHE; } ## **`$origin_server`** ## Define origin base URL from the request. ## This setup assumes that an origin server starts with an `@` symbol (such as `@nasa`, `@pinterest`, etc.). ## You can also add your own origin servers using [regular expressions](https://www.nginx.com/blog/regular-expression-tester-nginx/). map $uri $origin_server { default 'local://'; # Put your rewrite rules for origin servers from here. #> E.g. ~^/@mybucket/ 's3://my-bucket'; ~^/@myhost/ 'http://myhost.com'; ~^/@nasa/ 'https://mars.nasa.gov/system/downloadable_items'; ~^/@pinterest/ 'https://i.pinimg.com/originals'; } ## **`$origin_uri`** ## Parse real origin URI of the file. ## This setup just omits origin server and preset name in the URI if they exist, ## but you can also rewrite requested URI using [regular expressions](https://www.nginx.com/blog/regular-expression-tester-nginx/). map $uri_omitted_origin_preset $origin_uri { default '$uri_omitted_origin_preset'; # Put your rewrite rules for origin URI from here. #> E.g. this line rewrites cactus.jpg to the real path cacti.jpg. ~*^/cactus\.jpe?g$ '/cacti.jpg'; } ## **`$preset_name`** ## Parse preset name from requested URI. ## This setup assumes that preset name starts with an underscore (`_`) symbol (such as `_thumb` or `_w200`). ## You can define your own presets using [regular expressions](https://www.nginx.com/blog/regular-expression-tester-nginx/). map $uri_omitted_origin $preset_name { default ''; # You can define dynamic presets, #> but beware that dynamic presets are able to cause a denial-of-service attack #> by allowing an attacker to request multiple different image resizes. #> E.g. a dynamic preset with a variable $width. # ~^/_w(?[0-9_-]+)/ 'max_w:${parsed_width}'; # This is a better version for above dynamic preset. #> It allows only certain image sizes, #> and fallbacks other undefined image sizes to max_w:200 ~^/_w(?(200|640|800|1200|1600))/ 'max_w:${parsed_width}'; ~^/_w(?([0-9_-]+))/ 'max_w:200'; # Get static preset name from the URI ~^/_(?[a-z0-9_-]+)/ '$parsed_name'; } ## **`$imgproxy_preset`** ## Define `imgproxy` options for each preset name. ## You can view more details by following [their documentation](https://docs.imgproxy.net/usage/processing?#processing-options). map $preset_name $imgproxy_preset { default 'size:1600:0:0:0/preset:logo'; # preset:logo adds watermark to the image # Dynamic preset ~^max_w:(?[0-9]+)$ 'size:${width}:0:0:0/preset:logo'; # Static presets blurry 'size:320:320:1:0/blur:10/quality:50'; small 'size:320:320:0:0/sharpen:0.3/preset:logo'; medium 'size:640:640:0:0/preset:logo'; thumb 'size:160:160:1:1/bg:ffffff/resizing_type:fill/sharpen:0.3'; square 'size:500:500:0:1/bg:ffffff/resizing_type:fill/preset:logo'; masked 'size:500:0:0:1/bg:ffffff/resizing_type:fill/preset:repeated_logo'; download 'size:1600:0:0:0/preset:logo/return_attachment:1'; } ## **`$imgproxy_preset_query` (overriding presets with query string)** ## Override the `$imgproxy_preset` whenever `width` or `height` provided. ## But beware that dynamic image sizes are able to cause a denial-of-service attack by allowing an attacker to request multiple different image resizes. map "$arg_width:$arg_height" $imgproxy_preset_query { default ''; # only width ~^(?[0-9]+):$ '/size:${width}:0:1:0/bg:ffffff/resizing_type:fill'; # only height ~^:(?[0-9]+)$ '/size:0:${height}:1:0/bg:ffffff/resizing_type:fill'; # both width and height ~^(?[0-9]+):(?[0-9]+)$ '/size:${width}:${height}:1:0/bg:ffffff/resizing_type:fill'; } ## **`$imgproxy_quality` (overriding photo quality with query string)** ## Control photo quality with query string. You can also add your custom settings. map $arg_quality $imgproxy_quality { default ''; # if the given value is between 1 and 100, override the quality ~^(?[1-9][0-9]?|100)$ '/q:${quality}'; # or receive some readable quality values low '/q:30'; clear '/q:50'; high '/q:80'; highest '/q:100'; } ## **`$imgproxy_dpr` (controlling photo dimensions, aka Device Pixel Ratio)** ## This will multiplying the image dimensions according to this factor for HiDPI (Retina) devices. map $http_user_agent@$http_dpr $imgproxy_dpr { default '/dpr:1'; # parse from DPR header ~@(?[1-4]) '/dpr:${dpr}'; # Put your rewrite rules for DPR settings from here. #> E.g. these lines will set custom DPR for smartphones. # ~iPhone|iPad|Mac '/dpr:3'; # ~Android '/dpr:2'; } ## **`$imgproxy_extension`** ## Detect WebP or AVIF supports from the request header `Accept`. map $http_accept $imgproxy_extension { default ''; # uncomment this lines to enable WebP or AVIF compression # ~*webp '@webp'; # ~*avif '@avif'; } ## **`$imgproxy_options`** ## Generate final URL for `imgproxy` following [their documentation](https://docs.imgproxy.net/usage/processing). ## When URL query `?skip=1` is set, use another rule to skip `imgproxy` processing. map $arg_skip $imgproxy_options { default '/unsafe/${imgproxy_preset}${imgproxy_preset_query}${imgproxy_quality}${imgproxy_dpr}/plain/${origin_server}${origin_uri}${imgproxy_extension}'; ~.+ '/unsafe/plain/${origin_server}${origin_uri}'; } ##! **`$imgproxy_rewrite`** ## Generate final URL for `imgproxy` following [their documentation](https://docs.imgproxy.net/usage/processing). map $use_imgproxy $imgproxy_rewrite { default ''; 1 '$imgproxy_options'; } ##! **`$debug_imgproxy_rewrite`** ## For debugging the value of `$imgproxy_rewrite`. map $arg_debug $debug_imgproxy_rewrite { default ''; ~.+ '$imgproxy_rewrite'; } ## **`$fallback_uri`** ## Define fallback file to serve when the requested file is unavailable. ## E.g. `/noimage.jpg` or `/noimage_thumb.jpg`, which is stored in the folder `www/`. map $preset_name $fallback_uri { default '/noimage.jpg'; thumb '/noimage_thumb.jpg'; # small '/noimage_small.jpg'; # medium '/noimage_medium.jpg'; # square '/noimage_square.jpg'; } # Extra: You also can set `$fallback_uri` using value of `$origin_uri`. # map $origin_uri $fallback_uri # { # default '/noimage.jpg'; # ~^/products/ '/default_product_image.jpg'; # }