Hugo and Docker Development

I like to use Docker to manage my development environment for all of my projects. This allows me to pickup where I left off on a project without haing to spend the time getting my local machine back into the state it was when I was last working on the project. Docker also acts as documentation of the setup involved for my development environment.

I also start out with a monorepo with my Hugo project in a subfolder instead of the root.

My project folder starts off looking something like this:

root/
├─ hugo/
│  ├─ src/
│  │  ├─ .gitkeep
│  ├─ Dockerfile
├─ docker-compose.yml
├─ .env

The Dockerfile itself sets up a golang:bullseye container with Hugo and Node installed.

# hugo/Dockerfile
FROM golang:bullseye as hugo
WORKDIR /src

ARG NODE_VERSION=18.x
ENV NODE_VERSION ${NODE_VERSION}
RUN curl -sL https://deb.nodesource.com/setup_${NODE_VERSION} | bash - &&\
    apt-get update -y &&\
    apt-get install -y nodejs

# Install Hugo after Node to avoid Node installation everytime the Hugo version changes.
ARG HUGO_ARCH=amd64
ARG HUGO_VERSION=0.107.0
ENV HUGO_VERSION ${HUGO_VERSION}
RUN curl -L https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-${HUGO_ARCH}.deb -o hugo.deb &&\
    apt-get update -y &&\
    apt install ./hugo.deb

## NOTE: For new projects, you will need to comment the lines between the `start` and `end`. Otherwise you will get a build error.
## start: comment for new projects
# COPY ./src/package.json ./src/package-lock.json ./
# RUN npm install
# COPY ./src ./
# CMD npm start
## end: comment for new projects

Then my docker-compose.yml file starts off with just the hugo service.

# docker-compose.yml
version: '3.7'

services:

  hugo:
    build:
      context: ./hugo
      dockerfile: Dockerfile
      target: hugo
      args:
        HUGO_ARCH: ${HUGO_ARCH:-amd64}
    env_file:
      - .env
      - hugo/.env
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      HUGO_ENV: ${HUGO_ENV:-development}
      HUGO_BASEURL: ${HUGO_BASEURL}
    restart: unless-stopped
    volumes:
      - type: bind
        source: ./hugo/src
        target: /src
      ## NOTE: For new projects, you will need to comment the `/src/node_modules/` volumne
      # - /src/node_modules/

    tmpfs: /src/tmp
    ports:
      - 1313:1313

The last bit are the .env files in the root and hugo folders that will allow us to fine tune the setup based on our device. For instance, I ofetn develop on an AMD iMac and an M1 Macbook Pro on the road. By default the configuration is for the iMac.

touch .env
touch hugo/.env

On my M1 Macbook I have a .env file that changes the HUGO_ARCH evironment variable:

# .env (on my M1 Macbook)
HUGO_ARCH=arm64

With this configuration, you can also customize the NODE_VERSION, HUGO_VERSION, NODE_ENV, and HUGO_ENV.

The other environment variable to mention is HUGO_BASEURL so we dont have to set the baseURL in the hugo config.(toml|yaml|json) and the --baseURL flag when running the hugo development server.

With that we can run docker-compose build to build our container and docker-compose run --rm hugo bash to ssh into the container to run our hugo new site . to generate the hugo files in our hugo/src folder.

I have a few helper bash files I create to reduce the amount of docker-compose ... I have to write.

echo $'#!/usr/bin/env bash\ndocker-compose build "$@"' > build.sh
echo $'#!/usr/bin/env bash\ndocker-compose logs -f --tail=100 "$@"' > logs.sh
echo $'#!/usr/bin/env bash\ndocker-compose restart "$@"' > restart.sh
echo $'#!/usr/bin/env bash\ndocker-compose run --rm "$@"' > run.sh
echo $'#!/usr/bin/env bash\ndocker-compose stop "$@"' > stop.sh
echo $'#!/usr/bin/env bash\ndocker-compose up -d --build --force-recreate -V "$@"' > up.sh

chmod +x build.sh logs.sh restart.sh run.sh stop.sh up.sh

These scripts allow me to focus on the task and not have to memorize the docker commands. They all tak an optional service parameter which should match the name of your service in the docker-compose.yml file. If you do not pass the service name, it will run the command agains ALL services in your docker-compose.yml.

# Build all services in the `docker-compose.yml`
./build.sh

# Build JUST the `hugo` service
./build.sh hugo

I also named the service the same as the folder to allow autocomplete in the shell by using tab. Just be sure to hit backspace to remove the trailing /.