
Self-Hosting
Ich betreibe verschiedene Dienste um von den großen Firmen und der internationalen Politik unabhängig zu sein, aber auch um mein Wissen in diesem Bereich zu erhalten und vertiefen.
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.
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.
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:
.tar.gz
Archivs der generierten Dateien.latest
als auch als zeitgestempeltes Tag.So entsteht ein laufbereites Container-Image, das die Website über Nginx ausliefert – Deployments werden dadurch einfach und reproduzierbar.
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:
Das bedeutet: Änderungen zu veröffentlichen ist denkbar einfach:
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.