
Backend setup
As an example backend, we will use a little (node) starter project I wrote a while ago to learn React, called LeanJS. It is pre-packaged as a container image and it will serve us well for the demo purpose. You can, of course, use anything else that serves HTTP.
docker run --name backend -p 3080:3080 -d rdorgueil/leanjs
After a little while (the first run needs to download the rdorgueil/leanjs image), you should have a backend running as a background container, creatively named backend.
Open a browser and check that something answers on localhost:3080.

Good? Great.
Nginx / Pagespeed setup
One of the cons of ngx_pagespeed is that it requires to compile your own nginx with it, and probably you will get your systems dirty doing so.
The good news is I already wrote a little recipe to compile ngx_pagespeed along with nginx in a Debian package, based on the official nginx's Debian releases.
You can use the docker image factory to rebuild it for yourself, or just use the image from docker hub.
Note
Schematically, the okdocker/nginx recipe does the following:
- create a build container
- install the build dependencies
- download the source package
- adds ngx_pagespeed in the source
- changes the .deb manifest/metadata files
- compile the shit
- create a clean runtime container
- install the newly built Debian package
To use this image to serve an actual backend, you'll need to configure nginx. I usually start with the okdocker fork of H5BP's nginx configuration boilerplate that should cover pretty much all your basic needs.
Let's build and run a container based on this okdocker/nginx image:
git clone https://github.com/okdocker/server-configs-nginx.git nginx-container
cd nginx-container
make release
make run
That should be it, a nginx web server is now running on your docker engine's host (most probably locahost), listening to the :80 port, with a rather unimpressive page served.

Yet, you can look at the source and notice the difference (removed whitespaces and some obscure javascript used for instrumentation, mostly) with the original in the nginx config repository:

Plugging our backend — Where nginx is teinted with magic
You should still have the LeanJS backend running in a container, that should be named backend. If you don't, go back to the backend setup step and start over.
Let's change the default host to proxy our backend. Open sites-enabled/000_default (or sites-available/default, which is the former's symlink target) and add change it so it looks like this:
server {
listen [::]:80 default_server deferred;
listen 80 deferred;
# Path for static files
root /var/www/default;
# Specify a charset
charset utf-8;
# Custom 404 page
error_page 404 /404.html;
# Pagespeed basic config
include pagespeed/basic.conf;
include pagespeed/aggressive.conf;
include pagespeed/admin.conf;
# Pass requests to the internal backend proxy location.
location / {
try_files $uri @backend;
}
# Internal backend proxy location.
location @backend {
internal;
if ($request_filename ~* \.(jpg|jpeg|gif|png|bmp|ico|pdf|flv|swf|txt|css|js|otf|eot|svg|ttf|woff|woff2|map)$) {
expires 7d;
add_header Cache-control public;
access_log off;
}
include proxy/basic.conf;
proxy_pass http://backend:3080;
proxy_pass_header Cache-Control;
}
}
Build and run again the nginx container, with a docker link to the backend:
cd nginx-container
DOCKER_RUN_OPTIONS="--link backend:backend" make release run
Open again localhost:80 in your favorite web browser and stare for a few seconds at your backend's output.

Once again, you can have a look at the source and see whitespaces removed, instrumentation added, and assets rewritten (you may notice they're not on the first load, it is because pagespeed will progressively enhance the output over time, learning from what users actually request).

Amazing right? Isn't that octarine-esque?
Next in the serie: Amazon Cloudfront as our Content Delivery Network »
Caveats
Cache headers are relying less on browser, and more on nginx, which can lead to problems with resources you want to cache client side (I had problems with Edge Side Includes, for example).
pagespeed ModifyCachingHeaders off;
Beware though as this setting can have pretty unexpected side-effects on the pagespeed behavior. Try to avoid it if you can, and be conscious that you can cause yourself headaches with this setting.
Some pagespeed filters are riskier than others and can break your website, depending what you're already doing. You probably won't have any problems for small and very static websites, but if you have a lot of JavaScript magic, you can encounter a few problems. If you start to experience glitches, try disabling all the filters, see if it solves the problem, and if it does, reactivate the filters one by one (or bisect the activated filters to find the culprit, to debug it in log(n) passes instead of n).
For example, when getting this article ready, the remove_comments filter broke the React app. I first confirmed that pagespeed was the culprit by adding a pagespeed off; directive in the config, then bisected the active filters (by disabling half, re-enabling half of the half, etc.) to find the dubious filter. By following this process, you should not take more than a few minutes to debug pagespeed caused issues.