Hugo is a great static site generator and when you pair it with Git and a CI/CD pipeline you can create an automated deployment pipeline to update your live site whenever you add new content.

The pipeline here uses Drone as the automated build platform and Gitea as the Git server. These can be changed for whichever platform is required although the syntax may change the general steps should be the same.

First up a note about the directory structure. The site directory is generated using the default hugo new site command. This is placed in a subdirectory to make the repository look slightly cleaner overall.

Given that the end goal is to have a Docker image pushed to a repository (that way a service like watchtower can update the running container), there is the relevant Dockerfile and Docker ignore files.

|-- site/
|   |-- archetypes/
|   |-- config.toml
|   |-- content/
|   |-- data/
|   |-- layouts/
|   |-- static/
|   |-- themes/
|-- .dockerignore
|-- .drone.yml
|-- .gitignore
|-- Dockerfile


The Dockerfile is mostly taken from the Hugo Dockerfile but with an extra couple of steps. We want an NGINX image with the relevant built files within it so we will use a multi-stage Dockerfile. The following Dockerfile will build the latest version of Hugo within a container and then build our site. The arguments --minify and --gc just aid in creating smaller Docker images. Once built, the compiled site is compied from the ‘builder’ container and into a standard NGINX container.

FROM alpine:latest as builder
# Hugo versions
ENV HUGO_BINARY hugo_${HUGO_VERSION}_linux-64bit
# Install pygments for code highlighting
RUN apk update && apk add py-pygments && apk add bash && rm -rf /var/cache/apk/*
# Install Hugo
RUN mkdir /usr/local/hugo
ADD${HUGO_VERSION}/${HUGO_BINARY}.tar.gz /usr/local/hugo/
RUN tar xzf /usr/local/hugo/${HUGO_BINARY}.tar.gz -C /usr/local/hugo/ \
	&& ln -s /usr/local/hugo/hugo /usr/local/bin/hugo \
	&& rm /usr/local/hugo/${HUGO_BINARY}.tar.gz
# Copy hugo site
COPY site/ /app
# Build site
RUN hugo --minify --enableGitInfo --gc -d /app/build

# Create the webserver
FROM nginx:latest
# Copy built files
COPY --from=builder /app/build /usr/share/nginx/html


Most of the magic of the pipeline happens within the .drone.yml file. This is what defines the pipeline, what it will do and when it will run.

kind: pipeline
type: docker
name: default

  - name: test
    image: plugins/hugo
      validate: true
      pull: always
      source: site/

  - name: docker_push
    image: plugins/docker
      branch: master
      event: push
        from_secret: docker_username
        from_secret: docker_password
      repo: registry.your-domain/username/tag
      registry: registry.your-domain
      tags: latest

The first step uses Drone’s Hugo plugin to build our site. The advantage of this is that we can ensure the build compiles without errors in the first step, before attempting to build the image.

The second step builds the Docker image and pushes it to the a private registry. The username and password for the registry are taken from the secrets that Drone provides (in this case they were set through the settings menu of the Drone interface). Whenever a push is made to the repository, the test step will run, but not the Docker push step. This step will only run on a push to the master branch.

So now you can write everything, keep it backup up in a Git repository and automatically test, build (and deploy) the update site.