Gindlesperger

Hugo with Docker Compose

Some folks have asked how to properly host a hugo website using docker stack and serve it with caddy. I assume you already have a hugo site and a domain name that points to your server IP address.

There are docker containers that make it easy to build a simple development container, but hugo as a server is not designed for production use. It’s also not worth fighting against the development nature of the hugo server and the base URL of the site, etc.

Instead we build the site using hugo and then serve the static files with a production-ready web server like nginx. Lastly we securely expose the site to the internet using caddy as a reverse proxy.

Setup Caddy

Caddy is available in the official Ubuntu repositories starting with Ubuntu 24.04 LTS. For most users, installation is as simple as:

sudo apt update
sudo apt install caddy

Enable and start the Caddy service:

sudo systemctl enable --now caddy

Note: For best results, use Ubuntu 24.04 LTS or later. Older versions may require extra steps or a third-party repository.

By default Caddy provides /etc/caddy/Caddyfile, but it would be cumbersome to put all your site configurations in one file. Instead, we can create a directory to hold all our Caddy configurations:

sudo mkdir -p /etc/caddy/Caddyfile.d

Now, edit your main /etc/caddy/Caddyfile to import all configuration files from this directory:

import /etc/caddy/Caddyfile.d/*.caddyfile

You can now add individual site or service configs as separate .caddyfile files in /etc/caddy/Caddyfile.d/ for better organization and modularity.

After adding or changing configs, reload Caddy:

sudo systemctl reload caddy

Using Docker Compose for Hugo and Nginx

services:
  hugo:
    image: klakegg/hugo:ext-alpine
    container_name: foo-hugo
    volumes:
      - .:/src
    working_dir: /src

  nginx:
    image: nginx:alpine
    container_name: foo-nginx
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./public:/usr/share/nginx/html:ro
    depends_on:
      - hugo

Note that we expose port 80 of the nginx container to port 8080 of the host machine, but most importantly we limit it to 127.0.0.1 so that when running on the server it’s not exposed to the outside world, except through Caddy or any other reverse proxy.

Need an old person to work on your infrastructure? Contact me on BlueSky or e-mail me at kris@devrupt.io.