Intro

This is a first small technical writeup on how I managed to setup this website. I’m a system engineer with only a little knowledge about web frontend design using HTML / CSS / JS, but I still wanted something that didn’t look like a website from 1992… That’s why I chose to make use of a static site generator!

There are a couple out there that can be used but I opted for Hugo. This is an open source framework written in GO that converts Markdown files into HTML & CSS code.

Before starting this setup, I already had automation in mind. I wanted to create a git repository that would contain all needed files and would check & rebuilt the site whenever I add a new post or make a change. I made use of my selfhosted Gitlab server to host the repository and created a CI/CD job to check and built the website on every new push.

These files will then be used by a nginx docker container that will host the website.

Starting off

The site has to be created using the hugo new site x command. Moving into the newly created directory shows that the hugo site structure has been created!

hugo new site hugo-website

cd hugo-website

tree
.
├── archetypes
│   └── default.md
├── assets
├── config.toml
├── content
├── data
├── layouts
├── public
├── static
└── themes

Once inside the new directory I created a new repository and pushed it to my Gitlab server.

git init
git add .
git commit -m "initial commit"
git push --set-upstream http://{gitlab_IP}/{gitlab_namespace}/hugo-website master

Next up is adding a theme to the website. There are many to choose from here. The one I’m using is hello-friend-ng created by rhazdon.

git submodule add https://github.com/rhazdon/hugo-theme-hello-friend-ng.git themes/hello-friend-ng

CI/CD

The pipeline in my project creates the hugo website and uses rsync to ship it to the server where my nginx container is running. To use rsync within the pipeline we need to make use of SSH-keys which we will add as a variable.

A variable can be defined in the CI/CD section of the repository in the Gitlab GUI.

gitlab_vars

Here I created 3 variables which I will use in my CI file.

  • HUGO_SERVER
  • HUGO_USER
  • RSYNC_PRIVATE_KEY

CI file

By creating a .gitlab-ci.yml file inside the repository, the project can make use of a runner that triggers the CI pipeline on every push.

variables:
  GIT_SUBMODULE_STRATEGY: recursive

stages:
  - Build
  - Deploy

build:
  stage: Build
  image: registry.gitlab.com/pages/hugo:latest
  script:
    # Build the website   
    - hugo
  artifacts:
    paths:
      - ./public

deploy:
  stage: Deploy
  image: alpine:latest
  needs:
    - job: build
      artifacts: true
  before_script:
    # SSH client for rsync
    - apk add rsync openssh-client
    # Add the key
    - mkdir -p ~/.ssh
    - echo "$RSYNC_PRIVATE_KEY" > ~/.ssh/key
    - echo "StrictHostKeyChecking no" >> ~/.ssh/config
    - chmod -R 700 ~/.ssh
    - eval "$(ssh-agent -s)"
    - ssh-add ~/.ssh/key
  script:
    # Sync the built site
    - rsync -crtvz --delete ./public/ "$HUGO_USER"@"$HUGO_SERVER":~/webserver/public
  only: # Only run on main branch
    variables:
      - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Now the every new push in the repository will trigger a 2 step pipeline which will build & deploy the website.

  • Build

    • Use the hugo docker image to create the website
    • Save the public/ folder in artifacts (this will carry over to the next step)
  • Deploy

    • Collect artifacts
    • Use the alpine docker image
    • Add needed SSH client
    • Create needed SSH files + get private key from variable
    • Use rsync to copy over public/ folder to remote host

Serve content using nginx

After the CI/CD was in a working state, the website was ready to be started. Hugo itself can serve the website but I wanted to divide the creation and the serving of the content.

This could be really easily done by utilizing a nginx docker container and using the public/ directory as the content. I quickly made a docker-compose file which would do just that!

---
version: '3'

services:
  nginx:
    image: nginx:latest
    container_name: nginx
    ports:
      - 80:80
    volumes:
      - ~/webserver/public:/usr/share/nginx/html
    restart: unless-stopped

Ready!

This was pretty much it.

By making a new push to my main repository it will trigger a CI job which will update my site with the new changes!