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:

  1. Push changes to remote repo;
  2. Login to my server where this hugo site is hosted;
  3. Pull the changes for the git submodule;
  4. Run docker build to update my docker image with the new contents;
  5. 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