Skip to main content

New Hompage using Hugo and Forgejo

I'm running a small homepage and blog, primary as personal profile and self-marketing, but also to share my projects, experiences and learning. I want the homepage to be lightweight, fast and secure, and therefore I use a static site generator to render the contents. Since my homepage is primary my resume for self-marketing, I decided to go with careercanvas, created by Felipe Cordero, which is using the Hugo static site generator under the hood.

Since the carrercanvas is still work in progress, and doesn't work well with Hugo template projects, the best way at the moment is to fork Feilpes Homepage. I have done this, as a private repository on my self-hosted Forgejo instance. Since I also did some minor modifications of the carrercanvas theme, I also forked the carrercanvas.

To increase the comfort for updating the homepage, I created a Forgejo action, which is executed on my private Forgejo runner. This action looks like:

name: Build homepage container

on:
  push:
    tags:
      - '*'

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Default to bash
defaults:
  run:
    shell: bash

jobs:
  # Build job
  build:
    runs-on: docker
    env:
      HUGO_VERSION: 0.147.3
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Install Hugo CLI
        run: |
          wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
          && dpkg -i ${{ runner.temp }}/hugo.deb

      - name: Install Node.js dependencies
        run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"

      - name: Install podman
        run: |
          apt update
          apt install -y podman

      - name: Get date
        id: date_step
        run: echo "TAG=$(date '+%Y-%m-%d_%H-%M-%S')" >> $GITHUB_OUTPUT

      - name: Show tag
        run: echo ${{ steps.date_step.outputs.TAG }}

      - name: Build homepage
        run: |
          npm install
          rm -rf public
          npm run build:css
          npm run build
          tar -czf "tomirgang-${{ steps.date_step.outputs.TAG }}.tar.gz" public/*

      - name: Upload homepage artifact
        uses: actions/upload-artifact@v3
        with:
          name: tomirgang.tar.gz
          path: tomirgang-${{ steps.date_step.outputs.TAG }}.tar.gz

      - name: Build container
        run: |
          podman build -t git.tomirgang.de/tom/tomirgang:latest .

      - name: Build tag the container
        run: |
          podman tag git.tomirgang.de/tom/tomirgang:latest git.tomirgang.de/tom/tomirgang:${{ steps.date_step.outputs.TAG }}
          podman image ls


      - name: Upload latest container
        run: |
          podman push --creds tom:${{ secrets.HOMEPAGE_CONTAINER_TOKEN }} git.tomirgang.de/tom/tomirgang:latest docker://git.tomirgang.de/tom/tomirgang:latest

      - name: Upload date-tagged container
        run: |
          podman push --creds tom:${{ secrets.HOMEPAGE_CONTAINER_TOKEN }} "git.tomirgang.de/tom/tomirgang:${{ steps.date_step.outputs.TAG }}" "docker://git.tomirgang.de/tom/tomirgang:${{ steps.date_step.outputs.TAG }}"

It is automatically execute when I create a new tag, and does the following high level steps:

  • Prepare the fresh docker container with the dependencies. I use podman for building, since this works without issues and extended rights in a docker container.
  • The artifacts are tagged with a date timestamp, which is prepared in the "get date" step.
  • Then I run the hugo build in the "build homepage" step, which also creates a tarball of the generated files.
  • Next the generated HTML tarball is uploaded to the action, for easy inspection.
  • To deploy the homepage, I build a small nginx-base docker container which already includes the HTML, since this simplifies the deployment and update a lot. This container is build and published using the remaining steps.

The resulting container is provided using the container registry included in the Forgejo packages.

To serve the homepage, I use docker-compose.

services:
  homepage:
    image: git.tomirgang.de/tom/tomirgang:latest
    container_name: homepage
    ports:
      - 2210:80
    restart: unless-stopped

  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    restart: unless-stopped
    command: --interval 3600

The homepage container is using the container image created by the Forgejo action, pulling it from Forgejo packages. The watchtower container is taking care of updating the image. This container checks once an hour for updates for all running containers, and pulls the image and restarts the container if an image is available.

Overall, this means, to do a change to my homepage, I edit the sources, or create the new blog post, and commit the changes to my repository. When I want to publish it, I create a new release in my repository, which will create a tag, and trigger the Forgejo action above. This action will build and publish the container, and in worst case one hour later watchtower will notice the new container and update it, which will make the homepage update available.

Fun fact is that the homepage, the Forgejo instance and the Forgejo runner are all hosted on the same server, which is just a Hetzner CX22 for about 5€ per month.