Hugo Docker Image

Hugo is a popular open-source static site generator. A static site generator creates flat HTML files rather than relying on dynamic content like Javascript. Static sites typically have a smaller storage requirement and are more performant than dynamic websites, though they also have a much reduced feature set. However most people would say a site that supports full dynamic content that is used just for article content (like this site) would be a waste.

There are a fair few Hugo Docker images out there, but I decided to make my own to practice Dockerfile creation and to fully understand the image that I end up running. Please see my article regarding building a Docker image for the first time as a prerequisite to this build.

TAR file build-out

In addition to the Dockerfile for the .tar file I’ll be creating I also need to include several other files:

  • entrypoint.sh file contents detailed herein; script for container start
  • hugo binary downloaded from https://github.com/gohugoio/hugo/releases/latest
  • nginx.conf file contents detailed herein; main configuration options for Nginx webserver
  • net.redbarrel.knowhow.conf specific Nginx config for the site

SSH server is also installed so that the site contents can be managed by the administrator. Tips on defining content for a blog site in Hugo is covered in another article.

Dockerfile

FROM alpine:3.17.3

ENV SSHUSER=
ENV SSHPASSWORD=

RUN apk add --update --no-cache \
	git \
	gcompat \
	libc6-compat \
	libstdc++ \
	nginx \
	openssh

RUN echo 'PasswordAuthentication yes' >> /etc/ssh/sshd.config

RUN ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2

RUN mkdir /etc/nginx/sites-available /etc/nginx/sites-enabled

COPY nginx.conf /etc/nginx/

COPY net.redbarrel.knowhow.conf /etc/nginx/sites-available/

RUN ln -s /etc/nginx/sites-available/net.redbarrel.knowhow.conf /etc/nginx/sites-enabled/net.redbarrel.knowhow.conf

COPY hugo /usr/local/bin/

RUN chmod +x /usr/local/bin/hugo

COPY hugo-cron /etc/cron.d/hugo-cron
RUN chmod +x /etc/cron.d/hugo-cron
RUN crontab /etc/cron.d/hugo-cron
RUN touch /var/log/cron.log

COPY entrypoint.sh /

WORKDIR /srv

ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh

#!/bin/sh
adduser $SSHUSER
echo -n "$SSHUSER:$SSHPASSWORD" | chpasswd
chown $SSHUSER:$SSHUSER /srv
ssh-keygen -A
/usr/sbin/sshd -D -e "$@" > /dev/null 2>&1 &
/usr/sbin/crond -l 2 -L /var/log/cron.log
while true; do { if [ -d /srv/knowhow ] && [ -f /srv/knowhow/config.toml ]; then /usr/local/bin/hugo server -D -s /srv/knowhow --bind=0.0.0.0; break; else wait 30; fi } done > /dev/null 2>&1 &
nginx -g "daemon off;"

nginx.conf

user nginx;
worker_process auto;
pcre_jit on;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
	worker_connections 1024;
}

http {
	log_format main '$remote_addr - $remote_user [$time_local] "$request" '
					'$status $body_bytes_sent "$http_referer" '
					'"$http_user_agent" "$http_x_forwarded_for"';

	access_log /var/log/nginx/access.log main;

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	type_hash_max_size 2048;
	server_tokens off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*.conf;

	server {
		listen 80 default_server;
		listen [::]:80 default_server;
		
		server_name _;
		
		root /usr/share/nginx/html;
		
		include /etc/nginx/default.d/*.conf;
		
		location / {
		}
		
		error_page 404 /404.html;
			location = /40x.html {
		}
	}
}

net.redbarrel.knowhow.conf

server {
	listen 80;
	listen [::]:80;
	
	server_name knowhow.redbarrel.net;
	
	root /srv/knowhow/public;
	
	index index.html;
	
	access_log /var/log/nginx/www_access.log;
	error_log /var/log/nginx/www_error.log;
	
	location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bpm|rtf)$ {
		access_log off; log_not_found off; expires max;
	}
	
	location / {
		try_files $uri $uri/ =404;
	}
}

hugo-cron

* 4 * * * cd /srv/knowhow;/usr/local/bin/hugo --minify
Tip

The last line of the cron file must be an empty line in order to satisfy the syntax of the cron file

Portainer Build

  • Upload Dockerfile .tar to Portainer as hugo.custom
  • Create a docker volume called hugodata
  • Create a macvlan config network called br_knowhow_config
    • subnet: <subnet>
    • gateway: <gateway>
    • IP range: <ip range>
    • parent network card: <interface>.<vlan> (i.e. ens10.5)
  • Create a macvlan network based on br_knowhow_config caled br_knowhow
    • ☑ enable manual container attachment
  • Create VLAN in your firewall and switches, tagging through to your container host

Portainer Stack / Docker Compose Definition

version: "3"

services:
	hugo:
		image: hugo:custom
		volumes:
			- hugodata:/srv
		networks:
			- br_knowhow
		environment:
			- SSHUSER=<username>
			- SSHPASSWORD=<password>

volumes:
	hugodata:
		external: true

networks:
	br_knowhow:
		external:
			name: br_knowhow