Deploying Hugo with Docker on a VPS

hugo
docker
nginx
How to build a Hugo site into a multi-stage image and serve it with Nginx, without shipping the compiler inside the final image.
Author

Javier Iracheta

Published

31 Jul 2026

This is a sample post you can delete. It’s here to check that syntax highlighting, tables, and the table of contents all render properly.

Why multi-stage

Hugo generates static HTML. Once the site is compiled, the Hugo binary is no longer needed. With a two-stage build, the final image contains only Nginx and the generated files:

FROM hugomods/hugo:exts-0.154.5 AS builder
WORKDIR /src
COPY . .
RUN hugo --minify --gc

FROM nginx:alpine
COPY --from=builder /src/public /usr/share/nginx/html

The size difference is substantial:

Approach Approximate size
Image with Hugo included ~450 MB
Multi-stage with Nginx ~55 MB

The baseURL catch

Hugo writes the baseURL into the HTML at build time. If you compile with the default value and then serve from a different domain, the stylesheets point to the wrong place. That’s why it’s worth passing it as a build argument:

docker compose build --build-arg HUGO_BASEURL="https://yourdomain.com/"

Handy commands

# Write with live reload
docker compose -f compose.dev.yaml up

# Publish
docker compose up -d --build

If the site renders without styles after deploying, it’s almost always the baseURL. Check the <link rel="stylesheet"> in the served HTML.