Hello World! My first blog post will be about how i set up and hosted my site with hugo, docker and nginx.
There are many platforms to write a blog on - you can create your website from scratch or use a wordpress site, you can write on already established platforms like medium or dev. I did’t chose to write on those platforms as i wanted to learn something new. I have heard some good things about hugo and i decided to give it a shot.
Setting up hugo with a pre-built theme
Good thing about hugo is that there are plenty themes to choose from, that are available for anyone for free. Theme that i chose for my blog is PaperModX. Once i chose a theme i followed the quick-start guide on hugo documentation:
hugo new site <MySite>
cd <MySite>
git init
git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperModX.git themes/PaperMod
echo "theme = 'PaperModX'" >> config.toml
I have also changed the config format from toml to yaml, it’s just my personal preference and hugo works the same with any of those.
Next i have followed this guide to set-up some parameters on my config.yaml. Here is how my initial configuration looks like:
baseURL: "https://thesloth.me"
languageCode: "en-us"
title: "The Sloth Blog"
theme: "PaperModX"
params:
env: production
# main page cover:
cover:
linkFulImages: true
ShowBreadCrumbs: true
ShowReadingTime: True
ShowPostNavLinks: True
homeInfoParams:
Title: 'Welcome'
Content: "message on a main page"
socialIcons:
- name: github
url: 'github profile url'
- name: mastodon
url: 'mastodon profile url'
#menu items - order is set by assigning values to 'weight' parameter. Hugo prioritizes items by lowest weight
menu:
main:
- identifier: tags
name: Tags
url: /tags/
weight: 10
- name: Archive
url: /archive/
weight: 20
#favicons are placed in ./static directory. Images of posts are placed in ./static/img directory
assets:
favicon: "/favicon.ico"
favicon16x16: "/favicon-16x16.png"
favicon32x32: "/favicon-32x32.png"
After that i have configured an archive page - a page where all of my posts can be accessed. I have created ./content/archive.md
with the following contents:
---
title: 'Archive'
layout: 'archives'
url: '/archive/'
summary: archive
---
Writing new posts
Creating a new post is as easy as typing hugo new posts/my-post.md
. The command automatically creates my-post.md
markdown file in ./content/posts
- this is were all blog posts will be stored.
After typing up the contents of a post run a hugo
command to generate all of the html and css that is displayed on your site - MAGIC.
Hosting a site
I’m a big fan of docker, i use it to self-host all of my services, hence i will be using an NGINX webserver to host this site in a docker container on my own server. I have found a good write-up from Guenael Voisin - he did something similar for his hugo site so i used his configuration as an inspiration for my own.
I have the following Dockerfile
in the root of my hugo project:
FROM alpine:latest AS base
ENV HUGO_SITE=/srv/hugo
RUN apk --no-cache add \
git \
hugo \
&& mkdir -p ${HUGO_SITE} \
&& rm -rf /tmp/*
WORKDIR ${HUGO_SITE}
COPY . ${HUGO_SITE}
RUN hugo
FROM nginx:latest
COPY --from=base /srv/hugo/public /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
Running docker build -t myhugoserver .
builds an image with all project contents, runs hugo
command to generate a web-facing contents and runs an NGINX server with those contents.
I have a docker-compose.yml to run my docker image:
---
version: '3.0'
services:
blog:
image: hugoserver
ports:
- 1313:80
restart: unless-stopped
Since i’m hosting multiple services i already have port 80 occupied, therefore i mapped this docker container to the port 1313. I have a sepparate NGINX instance that i use as a reverse-proxy, this allows me to route the incomming web traffic to any port i want instead of just 80 or 443.
Reverse proxy
As already mentioned i have a separate container that runs NGINX as a reverse proxy, here’s how it’s docker-compose.yml
looks:
version: '3'
services:
nginx_reverse_proxy:
image: nginx:latest
volumes:
- ./conf.d:/etc/nginx/conf.d
- ./certbot/www:/var/www/certbot/
- ./certbot/conf/:/etc/nginx/ssl/
ports:
- "80:80"
- "443:443"
restart: unless-stopped
depends_on:
- certbot
certbot:
image: certbot/certbot:latest
volumes:
- ./certbot/www/:/var/www/certbot/
- ./certbot/conf/:/etc/letsencrypt/
I have a conf.d directory mapped with nginx.conf
inside. This is where i put all of my NGINX configuration. I don’t want expose all of it, but her is a boilerplate of how it looks:
This part is listening on port 80 for local services, so i can access them with domain names instead of ip addresses: For this to work local DNS resolver like pihole is also required
server {
listen 80;
server_name service1.lan;
location / {
proxy_pass "http://10.10.10.5:1234";
}
}
server {
listen 80;
server_name service2.lan;
location / {
proxy_pass "http://10.10.10.6:5678";
}
}
This is the part for this website setup with SSL, i’m listening on ports 80 and 443, but re-directing all of the traffic to port 443 to use encryption
server {
listen 80;
listen [::]:80;
server_name thesloth.me www.thesloth.me;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://thesloth.me$request_uri;
}
}
server {
listen 443 default_server ssl http2;
listen [::]:443 ssl http2;
server_name thesloth.me www.thesloth.me;
ssl_certificate /etc/nginx/ssl/live/thesloth.me/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/live/thesloth.me/privkey.pem;
access_log /var/log/nginx/thesloth.access.log;
location / {
proxy_set_header HOST $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass "http://10.0.0.7:1313";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "origin";
}
}
I’m using letsencrypt for my SSL sertificates, this guide helped me quite a bit when configuring NGINX with SSL.
Publishing new posts
I’ll admit that the current way of updating a blog with new posts is not the most convenient. I basically have a git repo for contents
directory which i have added as a git submodule in my hugo project. So when i write a new post i have to:
- Push changes to remote repo;
- Login to my server where this hugo site is hosted;
- Pull the changes for the git submodule;
- Run
docker build
to update my docker image with the new contents; - Restart my container with
docker-compose down && docker-compose up -d
I do realise this is far from perfect. I have some ideas on how to improve it - i will host my docker images in a repository and will try to implement some CI/CD magic, so that the build and restart happens automatically upon new commits.
The End
This was my first ever attempt to write a blog post, hopefully you enjoyed reading and found something useful. You can follow me and share your feedback on mastodon