Meine Homepage

Wie ich meine persönliche Homepage mit Hugo, Forgejo und Containern automatisiert habe

Ich betreibe eine kleine persönliche Homepage und einen Blog, der zwei Hauptzwecke erfüllt: ein persönliches Profil für Selbstmarketing zu präsentieren und gleichzeitig Projekte, Erfahrungen und Lerninhalte zu teilen.

Da meine Homepage leicht, schnell und sicher sein soll, habe ich mich für einen Static Site Generator entschieden. Nach einigem Recherchieren fiel meine Wahl auf careercanvas, ein Projekt von Felipe Cordero, das auf dem Hugo Static Site Generator basiert. Careercanvas eignet sich besonders gut für persönliche Lebensläufe und Entwicklerportfolios – perfekt für mein Vorhaben.

Einrichtung des Basisprojekts

Careercanvas ist noch in Arbeit und lässt sich nicht vollständig nahtlos mit Hugo-Template-Projekten kombinieren. Derzeit ist der empfohlene Weg, Felipe’s Homepage-Repository zu forken: felipecordero.github.io.

Ich habe es als privates Repository auf meiner selbstgehosteten Forgejo-Instanz (Forgejo) geforkt. Da ich zudem einige Anpassungen am Theme vorgenommen habe, habe ich auch careercanvas in meinem Namespace geforkt.

Automatisierung des Builds mit Forgejo Actions

Um das Veröffentlichen von Updates zu erleichtern, habe ich eine Forgejo Action erstellt, die auf meinem privaten Forgejo-Runner läuft. Jedes Mal, wenn ich einen neuen Tag im Repository erstelle, wird der Workflow automatisch ausgeführt und erledigt folgende Schritte:

...
    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: 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 }}"

Die groben Schritte der Action sind:

  1. Vorbereitung einer frischen Container-Umgebung mit allen notwendigen Abhängigkeiten (Hugo, Node.js, Podman).
  2. Erzeugung eines Zeitstempel-Tags für die Versionierung.
  3. Build der Seite mit Hugo, inklusive CSS, und Erstellung eines .tar.gz Archivs der generierten Dateien.
  4. Hochladen des Tarballs als Build-Artefakt zur einfachen Überprüfung.
  5. Build eines Nginx-basierten Containers, der bereits die statische Website enthält.
  6. Veröffentlichung des Containers im Forgejo-Registry, sowohl als latest als auch als zeitgestempeltes Tag.

So entsteht ein laufbereites Container-Image, das die Website über Nginx ausliefert – Deployments werden dadurch einfach und reproduzierbar.

Deployment mit Docker Compose und Watchtower

Auf dem Server läuft der Container über 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

Das Setup funktioniert so:

  • Der Homepage-Container zieht das neueste Image, das von meiner Forgejo Action erstellt wurde.
  • Watchtower prüft stündlich auf Updates. Wird ein neues Container-Image gefunden, zieht es automatisch das Image und startet den Container neu.

Das bedeutet: Änderungen zu veröffentlichen ist denkbar einfach:

  1. Inhalte bearbeiten oder neuen Blogpost erstellen.
  2. Änderungen committen und pushen.
  3. Release erstellen (dies löst die Forgejo Action aus).
  4. Innerhalb einer Stunde aktualisiert Watchtower automatisch den Container.

Hosting Setup

Besonders spannend: Homepage, Forgejo-Instanz und Forgejo-Runner laufen alle auf demselben Server – einem Hetzner CX22 für ca. 5 € pro Monat.

Das Ergebnis ist ein kostengünstiges, selbstverwaltetes System, das volle Kontrolle bietet, automatisierte Builds und Deployments ermöglicht und kaum manuelle Eingriffe erfordert.