Docker, nginx, SPA and brotli compression
Contemporary web development separates front-end and back-end, resulting in the front-end being a few static files. Besides setting long cache headers, pre-compression is one way to speed up delivery
Setting the stage
- we have a NodeJS project that outputs our SPA in
/usr/dist
directory. Highly recommended here: VITE. Works for multi-page applications too. - We target only modern browsers that understand brotli (Sorry not IE). Legacy will have to deal with uncompressed files
- We want to go light on CPU, so we compress at build time, not runtime
Things to know
- When nginx is configured for brotli and the file
index.html
gets requested, the fileindex.html.br
gets served if present and the browser indicated (what it does by default) that it can accept br - There are tons of information about the need to compile nginx due to the lack of brotli support out of the box. That's not necessary (see below)
- brotli is both OpenSource and the open standard RFC 7932
- brotli currently lacks gzip's
-r
flag, so some bash magic is needed
Moving parts
- DockerFile
- nginx configuration
The Dockerfile will handle the brotli generation
Dockerfile
# build container using an LTS Node version
# does not get deployed to runtime
FROM node:18-alpine AS builder
# Make sure we ot brotli
RUN apk update
RUN apk add --upgrade brotli
# Create app directory
WORKDIR /usr
COPY package*.json ./
COPY src ./src
COPY public ./public
RUN npm install
RUN npm run build
RUN cd /usr/dist && find . -type f -exec brotli -v -Z {} \;
# Actual runtime container
FROM alpine
RUN apk add brotli nginx nginx-mod-http-brotli
#COPY nginx/*.conf /etc/nginx/
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY nginx/error404.* /usr/share/nginx/html/
COPY nginx/favicon.* /usr/share/nginx/html/
# Actual data
COPY --from=builder /usr/dist /usr/share/nginx/html/
CMD ["nginx", "-g", "daemon off;"]
EXPOSE 80
nginx.conf
# nginx.conf
# Adjusted nginx.conf, serve on site out of
# a container with static brotli, no regex, no dynamic compression
# comments removed check the nginx documentation https://nginx.org/en/docs/
# Error and event logs to console for Docker to capture
user nginx;
worker_processes auto;
pcre_jit off;
error_log /dev/stdout info;
include /etc/nginx/modules/*.conf;
include /etc/nginx/conf.d/*.conf;
events {
worker_connections 1024;
}
http {
access_log /dev/stdout;
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
client_max_body_size 10m;
sendfile on;
tcp_nopush on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:2m;
ssl_session_timeout 1h;
ssl_session_tickets off;
gzip off;
gzip_vary off;
brotli_static on;
# Helper variable for proxying websockets.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
server {
listen 80 default_server;
listen [::]:80 default_server;
root /usr/share/nginx/html;
error_page 404 /error404.html;
location / {
try_files $uri $uri/ =404;
}
}
}
Insights
- I valiantly fought to get it to work with the official nginx image
nginx.alpine
, but that didn't work, so I had to fall back to alpine and install nginx and the module from the same repo - Compression sizes are impressive. E.g. svg files shrink by 50%
- source code is available on GitHub
Sources:
As usual YMMV
Posted by Stephan H Wissel on 24 June 2023 | Comments (0) | categories: Docker nginx WebDevelopment