Originally posted on Reyes.
After searching for a bit I was unable to find a nice pre-made Dockerfile to serve my personal site (built on top of Hugo), some of the images I found were only Hugo build steps, some others were able to serve and build the site but they pulled the FROM:ubuntu
docker anti-pattern.
So here I'll describe what I'm doing on my final Dockerfile, it's a really simple Docker Multi-Stage build, the first step gets the Hugo binary and builds the site, the second one copies over the public folder of the built site and serves it using the official alpine Nginx image.
What's a multi-stage build?
With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image. To show how this works, Let’s adapt the Dockerfile from the previous section to use multi-stage builds.
Docker
Hugo Build Step
FROM alpine:3.5 as build
ENV HUGO_VERSION 0.41
ENV HUGO_BINARY hugo_${HUGO_VERSION}_Linux-64bit.tar.gz
# Install Hugo
RUN set -x && \
apk add --update wget ca-certificates && \
wget https://github.com/spf13/hugo/releases/download/v${HUGO_VERSION}/${HUGO_BINARY} && \
tar xzf ${HUGO_BINARY} && \
rm -r ${HUGO_BINARY} && \
mv hugo /usr/bin && \
apk del wget ca-certificates && \
rm /var/cache/apk/*
COPY ./ /site
WORKDIR /site
RUN /usr/bin/hugo
The first part FROM alpine:3.5 as build
it's labeling that build step as build
so that in the next step we can get artifacts from this previous image, the artifact we'll be keeping is the built site.
ENV HUGO_VERSION 0.41
ENV HUGO_BINARY hugo_${HUGO_VERSION}_Linux-64bit.tar.gz
This is pretty self-explanatory, declare the Hugo version and use it to get the binary name that we're gonna get from the official GitHub releases
RUN set -x && \
apk add --update wget ca-certificates && \
wget https://github.com/spf13/hugo/releases/download/v${HUGO_VERSION}/${HUGO_BINARY} && \
tar xzf ${HUGO_BINARY} && \
rm -r ${HUGO_BINARY} && \
mv hugo /usr/bin && \
apk del wget ca-certificates && \
rm /var/cache/apk/*
set -x
will give us a nice output for these commands, then we are updating the repos and installing wget
and ca-certificates
from the alpine repositories, after that we're building the release binary download URL from the GitHub releases page and downloading it using wget, then we're uncompressing the tar file, deleting it and moving the binary to the /usr/bin folder, after that we do some standard clean-up tasks to get the final image to the lower size we can without much effort.
COPY ./ /site
WORKDIR /site
RUN /usr/bin/hugo
Now we're copying the current directory (should be the Hugo site root folder) and moving it into /site (inside the container), next we're basically doing a change-dir to /site
and the last step is the one that actually runs the Hugo binary against /site and that gets us the final /public
folder with the assets that we're gonna server over Nginx.
Nginx Build Step
This one is pretty simple and it's the second and last step, we're gonna be using the official Alpine Nginx repo to get a container with the reverse-proxy installed
FROM nginx:alpine
LABEL maintainer Eduardo Reyes <eduardo@reyes.im>
COPY ./conf/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /site/public /var/www/site
WORKDIR /var/www/site
This one is really simple, we're getting the official nginx:alpine
image and using it as our second build step, then I'm adding my maintainer label to the final container, copying over a Nginx configuration file over the container, the important step is this one.
COPY --from=build /site/public /var/www/site
This step is getting the public folder (the artifact we built on the first step) --from
the build
step of the multi-stage build and copying it over /var/www/site
were Nginx will serve it as static content thanks to the basic configuration file copied earlier and then we're changing dirs into the site, so that if we want to go into the container we're already where the code is.
Final Dockerfile
FROM alpine:3.5 as build
ENV HUGO_VERSION 0.41
ENV HUGO_BINARY hugo_${HUGO_VERSION}_Linux-64bit.tar.gz
# Install Hugo
RUN set -x && \
apk add --update wget ca-certificates && \
wget https://github.com/spf13/hugo/releases/download/v${HUGO_VERSION}/${HUGO_BINARY} && \
tar xzf ${HUGO_BINARY} && \
rm -r ${HUGO_BINARY} && \
mv hugo /usr/bin && \
apk del wget ca-certificates && \
rm /var/cache/apk/*
COPY ./ /site
WORKDIR /site
RUN /usr/bin/hugo
FROM nginx:alpine
LABEL maintainer Eduardo Reyes <eduardo@reyes.im>
COPY ./conf/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /site/public /var/www/site
WORKDIR /var/www/site
Building the image
This one is a really simple docker command, you should run it from the root of your Hugo site.
docker build -t some-hugo-site .
and to see the results just do:
docker run -d -p 8080:80 some-hugo-site
I recommend using traefik to expose the container to the web, take advantage of the automatic let's encrypt integration and add a http -> https redirect to get a nice TLS workflow.
Top comments (2)
how about ?
COPY ./conf/default.conf /etc/nginx/conf.d/default.conf
My project do not have such folder conf - what I'm missing here?
Hi ! I have a problem with this code.
When
COPY --from=build /site/public /usr/share/nginx/html
The site/public is not copied to /usr/share/nginx/html
Any idea ?