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