Skip to main content

Serve static files using Nginx with Traefik

·4 mins
Gerald Gnaegy
Author
Gerald Gnaegy
I love solving problems and blog here about Python, Docker, IT automation and technology.
Table of Contents

Instead of using a blogging engine like Wordpress, my website is created with a static site generator called Hugo. I don’t need to setup a database and my website is fast and secure, because I just need to serve static files.

In this setup, Traefik is configured as a reverse proxy, responsible for managing SSL certificates. Nginx is serving the static content from a directory. Everything is dockerized, of course.

We’ll start with a basic setup and further fine-tune the Nginx configuration.

Create the project structure
#

mkdir -p $HOME/docker-projects/blog/www
cd docker-projects/blog

You should have the following structure:

├── blog
    ├── docker-compose.yml
    └── www

In the www directory you can place your static files (e.g. HTML files, images).

For testing purposes, create a index.html file in www/:

echo "<h1>HelloWorld</h1>" >> www/index.html

Very basic setup
#

Now, we create a docker-compose.yml in docker-projects/blog:

services:
  blog:
    container_name: "blog"
    image: nginx:1.26.1
    volumes:
      - ./www:/usr/share/nginx/html:ro
    networks:
      - docknet
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.blog.rule=Host(`example.com`)'
      - 'traefik.http.routers.blog.entrypoints=https'
      - 'traefik.http.routers.blog.tls=true'
      - 'traefik.http.routers.blog.tls.certresolver=letsencrypt'
    expose:
      - 80

networks:
  docknet:
    external: true

Start the container using docker compose up -d:

docker compose up -d

Open your domain in a web browser to verify that you can see the index.html page displaying Hello World.

Add nginx.conf configuration file
#

Update docker-compose.yml to add a nginx.conf configuration file:

services:
  blog:
    container_name: "blog"
    image: nginx:1.26.1
    volumes:
      - ./www:/usr/share/nginx/html:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks:
      - docknet
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.blog.rule=Host(`ggn92.dev`)'
      - 'traefik.http.routers.blog.entrypoints=https'
      - 'traefik.http.routers.blog.tls=true'
      - 'traefik.http.routers.blog.tls.certresolver=letsencrypt'
    expose:
      - 80

networks:
  docknet:
    external: true

Add the Nginx configuration file nginx.conf:

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

Restart your Docker container using docker compose restart and check the logs with docker logs blog to make sure that everything is healthy:

docker compose restart
docker logs blog

Enable compression in Nginx
#

Compressing responses often significantly reduces the size of transmitted data. It may be possible to gain performance benefits by enabling it in Nginx.

This is a response of my server without enabling compression:

curl -H "Accept-Encoding: gzip" -I  https://ggn92.dev/

HTTP/2 200
accept-ranges: bytes
content-type: text/html
date: Fri, 09 Aug 2024 14:53:39 GMT
etag: "66b62859-63f5"
last-modified: Fri, 09 Aug 2024 14:31:53 GMT
server: nginx/1.26.1
content-length: 25589

We’ll set gzip to on and provide some additional configuration parameters:

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_proxied any;
    gzip_min_length 1000;
    gzip_comp_level 5;
    gzip_vary on;

    server {
        listen       80;
        server_name  localhost;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

Again, restart your Docker container and check the logs to make sure that everything is healthy:

docker compose restart
docker logs blog

And here is the result with enabled compression:

curl -H "Accept-Encoding: gzip" -I  https://ggn92.dev/

HTTP/2 200
content-encoding: gzip
content-type: text/html
date: Fri, 09 Aug 2024 14:59:04 GMT
etag: W/"66b62859-63f5"
last-modified: Fri, 09 Aug 2024 14:31:53 GMT
server: nginx/1.26.1
vary: Accept-Encoding

Hide Nginx version
#

Did you notice that we’re using nginx/1.26.1? We can omit that:

Add this line inside the http section of nginx.conf:

server_tokens off;

Restart the container and check the result:

docker compose restart
curl -I  https://ggn92.dev/

HTTP/2 200
accept-ranges: bytes
content-type: text/html
date: Fri, 09 Aug 2024 15:57:27 GMT
etag: "66b62859-63f5"
last-modified: Fri, 09 Aug 2024 14:31:53 GMT
server: nginx
vary: Accept-Encoding
content-length: 25589

Thank you
#

I appreciate your feedback on this tutorial. If you have suggestions, improvements, or encounter any bugs, please don’t hesitate to reach out. I’ll strive to keep this guide updated with the latest best practices and features.

Thank you for following along, and I hope this tutorial has been helpful in setting up your Nginx Docker container. Happy deploying!