Logo
Loading...
Published on

Adding Proxy to serve images as webp

Author

So I've decided to test my blog on Lighthouse and page speed and they both recommend using webp format for images, but how can we do this for blog posts?

So first idea that comes to our head - well let's just convert our jpg's and png's when user uploads file to webp's. And while it's viable idea, we will need to make alot of adjustments for our code.

Second idea is - can we generate webp from image on the flight? Yes we can! Also there some good providers of alot of on the flight image processing functionalities like Cloudinary, we are small blog and we don't need it.

Golang is really fast and there is good aplication which achieves exactly this - github.com/webp-sh/webp_server_go

You can find documentation here - docs.webp.sh/

So to start of we will create our docker-compose.yml file:

version: '3'

services:
  webp:
    image: webpsh/webp-server-go
    # image: ghcr.io/webp-sh/webp_server_go
    restart: always
    environment:
      - MALLOC_ARENA_MAX=1
      - LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
      - WEBP_QUALITY=100
      # - LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4.5.6
    volumes:
      - /var/www/storage:/opt/pics/shared
      - ./exhaust:/opt/exhaust
      - ./metadata:/opt/metadata
    ports:
      -  127.0.0.1:3333:3333

It's pretty straightforward, you can provide your config file or just use env variables.

Then just run it with docker-compose up -d.

You can also use binary and run it as a service, but docker is more preferable here. If docker service enabled in systemd it will respect restart: always directive and start server automatically.

To chack logs ou can use docker-compose logs --tail 10 -f

So now we need to set up our NGINX to proxy our images requests to our go application:

    location ~* ^/shared/(.*\.(?:svg|heic|bmp|nef|webp))$ {
        root /var/www/storage/;
        try_files /$1 =404;
        expires 1y;
        etag on;
        if_modified_since exact;
        add_header Cache-Control "public, no-cache";
    }

    location ~* ^/shared/(.*\.(?:jpg|jpeg|gif|png))$ {
        proxy_pass http://127.0.0.1:3333;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_hide_header X-Powered-By;
        proxy_set_header HOST $http_host;
        add_header Cache-Control 'public, no-cache';
        expires 1y;
        etag on;
        if_modified_since exact;
        proxy_intercept_errors on;
        error_page 502 = @fallback;
    }

    location @fallback {
        root /var/www/storage/;
        try_files /$1 =404;
        expires 1y;
        etag on;
        if_modified_since exact;
        add_header Cache-Control "public, no-cache";
    }

I'll explain a little because it can be qite complicated.

First location serves files directly which i don't want to convert to webp. Regex captures group from URI to $1 which is our path ot file in system.

Second location serves files via proxy to our Go Webp Server, so our png's will be converted to webp's on the flight, cool!

proxy_intercept_errors on; allows us to catch and forward proxy errors so we can show our nice looking error pages!

What if our proxy service is down? We will get 502 error for our images request, which will kinda break our website. We don't want this, so will implement fallback to serve our files directly as is. Now when our proxy down we still see images, even tho they are not webp.

Awesome, you can build fully functional proxy using this application, also you can pass parameters like width/height to get crops on the flight, which is super cool!