Introduction

Alright I lied, I’ll do another blog post, seeing as you all asked so nicely! This post expands upon starting-a-hugo-site and will show you have to package up HUGO in docker, I mean sure you can just upload it to S3 in AWS and run as a static page, but if you’re like me you want to mess around with shiny things and I guess Docker is shiny?

Getting Started

Install Docker

Homebrew is a package manager for macOS, can be installed from brew.sh.

brew install docker

To verify your new install:

docker -v

My output: Docker version 20.10.8, build 3967b7d

Make it so

Create Dockerfile

Google says

A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession. This page describes the commands you can use in a Dockerfile .

So there you go? Inside the root of your repository create a file called Dockerfile add the below contents!

FROM alpine:3.9 AS build

ARG VERSION="0.92.0"

ADD https://github.com/gohugoio/hugo/releases/download/v${VERSION}/hugo_${VERSION}_Linux-64bit.tar.gz /hugo.tar.gz
RUN tar -zxvf hugo.tar.gz
RUN /hugo version

RUN apk add --no-cache git

COPY . /site
WORKDIR /site

RUN /hugo 

FROM nginx:1.15-alpine

WORKDIR /usr/share/nginx/html/

RUN rm -fr * .??*

RUN sed -i '9i\        include /etc/nginx/conf.d/expires.inc;\n' /etc/nginx/conf.d/default.conf

COPY _docker/expires.inc /etc/nginx/conf.d/expires.inc
RUN chmod 0644 /etc/nginx/conf.d/expires.inc

COPY --from=build /site/public /usr/share/nginx/html

Lets walk through this! FROM alpine:3.9 AS build - The FROM instruction specifies the Parent Image from which you are building.

The next section grabs the hugo binary based on the version you wish to run (try to match version to the one you’re using locally!)

ARG VERSION="0.92.0"
ADD https://github.com/gohugoio/hugo/releases/download/v${VERSION}/hugo_${VERSION}_Linux-64bit.tar.gz /hugo.tar.gz
RUN tar -zxvf hugo.tar.gz
RUN /hugo version

We add git to the build stage, because Hugo needs it with –enableGitInfo RUN apk add --no-cache git

The source files are copied to /site and then we run hugo!

COPY . /site
WORKDIR /site
RUN /hugo

Then its onto stage2:

FROM nginx:1.15-alpine
WORKDIR /usr/share/nginx/html/

The above is setting the WORKDIR. The WORKDIR instruction sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it in the Dockerfile. If the WORKDIR doesn’t exist, it will be created even if it’s not used in any subsequent Dockerfile instruction. We then cleanup the existing nginx: RUN rm -fr * .??*

This inserts a line in the default config file, including our file “expires.inc” RUN sed -i '9i\ include /etc/nginx/conf.d/expires.inc;\n' /etc/nginx/conf.d/default.conf

The file “expires.inc” is copied into the image

COPY _docker/expires.inc /etc/nginx/conf.d/expires.inc

RUN chmod 0644 /etc/nginx/conf.d/expires.inc

Finally, the “public” folder generated by Hugo in the previous stage is copied into the public fold of nginx COPY --from=build /site/public /usr/share/nginx/html

If you want to understand more about multi-stage builds i’d reccomend this page.

So this was kinda overkill but hopefully i’ve walked you through it enough! Credit to this person for the majority of the above.

The Dockerfile is multi-stage, keeping the image size down.

  • The first stage builds the static website with Hugo.
  • The second stage configures nginx to serve the static pages.

This line:

RUN sed -i '9i\        include /etc/nginx/conf.d/expires.inc;\n' /etc/nginx/conf.d/default.conf

will update /etc/nginx/conf.d/default.conf from this:

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

into this:

    location / {
        include /etc/nginx/conf.d/expires.inc;

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

This is the expires.inc file, inspired stolen double stolen from https://serversforhackers.com/c/nginx-caching

# cache.appcache, your document html and data
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
  expires -1;
}

# Feed
location ~* \.(?:rss|atom)$ {
  expires 1h;
  add_header Cache-Control "public";
}

# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
  expires 1M;
  access_log off;
  add_header Cache-Control "public";
}

# CSS and Javascript
location ~* \.(?:css|js)$ {
  expires 1y;
  access_log off;
  add_header Cache-Control "public";
}

Further reading here: https://serversforhackers.com/c/nginx-caching:

To finish up create this directory in the root of your repository: mkdir _docker Create expires.inc and add this content:

# cache.appcache, your document html and data
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
  expires -1;
}

# Feed
location ~* \.(?:rss|atom)$ {
  expires 1h;
  add_header Cache-Control "public";
}

# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
  expires 1M;
  access_log off;
  add_header Cache-Control "public";
}

# CSS and Javascript
location ~* \.(?:css|js)$ {
  expires 1y;
  access_log off;
  add_header Cache-Control "public";
}

Building Docker image

Building the docker image is straight forward just run this bad boy: docker build . -t playground-app

Output should look something like this:

Sending build context to Docker daemon   16.1MB
Step 1/16 : FROM alpine:3.9 AS build
 ---> 78a2ce922f86
Step 2/16 : ARG VERSION="0.92.0"
 ---> Using cache
 ---> 7ef59ed77321
Step 3/16 : ADD https://github.com/gohugoio/hugo/releases/download/v${VERSION}/hugo_${VERSION}_Linux-64bit.tar.gz /hugo.tar.gz
Downloading [==================================================>]  15.14MB/15.14MB

 ---> Using cache
 ---> bf8e1a1e3339
Step 4/16 : RUN tar -zxvf hugo.tar.gz
 ---> Using cache
 ---> c858ef93b348
Step 5/16 : RUN /hugo version
 ---> Using cache
 ---> 5dd97bc3b5ab
Step 6/16 : RUN apk add --no-cache git
 ---> Using cache
 ---> b013b3225328
Step 7/16 : COPY . /site
 ---> 7fdbc571761b
Step 8/16 : WORKDIR /site
 ---> Running in 67f3c6739f34
Removing intermediate container 67f3c6739f34
 ---> b8d6f18ad6ee
Step 9/16 : RUN /hugo
 ---> Running in b863dd49427b
Start building sites …
hugo v0.92.0-B3549403 linux/amd64 BuildDate=2022-01-12T08:23:18Z VendorInfo=gohugoio

                   | EN
-------------------+-----
  Pages            |  7
  Paginator pages  |  0
  Non-page files   |  0
  Static files     |  0
  Processed images |  0
  Aliases          |  1
  Sitemaps         |  1
  Cleaned          |  0

Total in 160 ms
Removing intermediate container b863dd49427b
 ---> 70e9511a61f8
Step 10/16 : FROM nginx:1.15-alpine
 ---> dd025cdfe837
Step 11/16 : WORKDIR /usr/share/nginx/html/
 ---> Using cache
 ---> 061d522a46fb
Step 12/16 : RUN rm -fr * .??*
 ---> Using cache
 ---> c2c75b6344b5
Step 13/16 : RUN sed -i '9i\        include /etc/nginx/conf.d/expires.inc;\n' /etc/nginx/conf.d/default.conf
 ---> Using cache
 ---> 1ffa07a1c4de
Step 14/16 : COPY _docker/expires.inc /etc/nginx/conf.d/expires.inc
 ---> Using cache
 ---> 88af2f4403de
Step 15/16 : RUN chmod 0644 /etc/nginx/conf.d/expires.inc
 ---> Using cache
 ---> cb003b895b1e
Step 16/16 : COPY --from=build /site/public /usr/share/nginx/html
 ---> 14b9037a4f66
Successfully built 14b9037a4f66
Successfully tagged playground-app:latest

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

Running Docker image

To run the container run the below command: docker run -p 8989:80 playground-app

You should now be able to access your container on http://localhost:8989