Warum Gitea der erste Dienst war, den ich auf meinem Homeserver installiert habe
Du willst einen Homeserver aufsetzen und am Ende soll da eine Handvoll Dienste laufen? Du willst die Konfiguration der Dienste nachvollziehen und versionieren können? Und du hast keine Lust, jedes Deployment manuell per SSH zu erledigen?
Dann ist Gitea vielleicht interessant für dich.
Ich hatte schon länger mit dem Gedanken gespielt, einen Homeserver einzurichten. Ich hatte auch eine Handvoll Dienste im Sinn, die ich in meinem Heimnetzwerk hosten möchte. Doch mit welchem sollte ich anfangen? Letztlich fiel meine Wahl auf Gitea.
Gitea ist ein selbst gehosteter Git-Server. Eine leichtgewichtige Alternative zu GitHub oder GitLab. Es gibt Repositories, Issues, Pull Requests, eine Web-UI und vor allem: Gitea Actions.
Der Gedanke hinter der Reihenfolge war folgender: Wenn ich sowieso jeden Dienst als Docker Compose Setup verwalten will, kann ich das auch in Git pflegen. Und wenn ich es in Git pflege, kann ich Deployments mithilfe von Actions automatisieren. Und mit automatisierten Deployments wird dann die Installation aller anderen Dienste deutlich einfacher.
Also: Gitea zuerst. Damit wird jeder danach einfacher.
Gitea installieren
Die Installation läuft bei mir über Docker Compose.
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=gitea-db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=${DB_PASSWORD}
- GITEA__server__DOMAIN=gitea.example.com
- GITEA__server__ROOT_URL=https://gitea.example.com
- GITEA__server__SSH_DOMAIN=gitea.example.com
- GITEA__actions__DEFAULT_ACTIONS_URL=self
ports:
- "8010:3000"
- "2222:22"
volumes:
- /mnt/data/gitea:/data
gitea-db:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=gitea
volumes:
- /mnt/data/gitea-db:/var/lib/postgresql/data
Nach docker compose up -d ist die Web-UI unter Port 3000 erreichbar und führt durch einen Einrichtungsassistenten.
Einen Punkt gilt es hier zu beachten: DEFAULT_ACTIONS_URL=self. Diese Einstellung bewirkt, dass Gitea Actions aus dem eigenen Gitea lädt statt von github.com. Das ist später wichtig, wenn man eigene Actions schreibt.
Den Runner einrichten
Gitea Actions braucht einen Runner. Der läuft als eigener Container und führt die Workflows aus.
services:
runner:
image: gitea/act_runner:latest
container_name: gitea-runner
restart: unless-stopped
extra_hosts:
- "gitea.example.com:192.168.x.y"
environment:
- GITEA_INSTANCE_URL=https://gitea.example.com
- GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN}
- CONFIG_FILE=/data/config.yml
volumes:
- /mnt/data/gitea-runner:/data
- /var/run/docker.sock:/var/run/docker.sock
Das extra_hosts ist nötig, damit der Runner-Container lokale Domains auflösen kann. Ohne das schlägt schon das Auschecken des Repos fehl, weil gitea.example.com außerhalb des Containers nicht aufgelöst wird.
Den RUNNER_TOKEN gibt es in Gitea unter Administration > Actions > Runner > Neuen Runner erstellen.
Wenn der Runner läuft, taucht er dort mit Status Idle auf. Ab jetzt können Workflows ausgeführt werden.
Die Pipeline: ein Muster für alle Dienste
Das Ziel war ein Deploy-Workflow, den ich für jeden Dienst wiederverwenden kann. Kein Copy-Paste von SSH-Befehlen, kein dupliziertes Setup.
Dafür gibt es das Repo homeserver/homeserver-actions. Es enthält zwei Dinge:
Ein Docker-Image (deploy-tools) mit rsync, openssh-client und git. Dieses Image wird für alle Deploy-Jobs verwendet.
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
rsync \
openssh-client \
git \
&& rm -rf /var/lib/apt/lists/*
Eine Composite Action (deploy/action.yml), die den eigentlichen Deploy macht: SSH-Key einrichten, Dateien per rsync übertragen, Remote-Befehl ausführen.
Das deploy-tools Image wird in der Gitea Container Registry gespeichert und von dort in jedem Workflow geladen.
Wie ein Deploy-Workflow aussieht
Im Repository jedes Dienstes liegt eine Datei .gitea/workflows/deploy.yml. Die sieht etwa so aus:
name: Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
container:
image: gitea.example.com/homeserver/deploy-tools:latest
credentials:
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
steps:
- name: Checkout
run: git clone https://x-token-auth:${{ github.token }}@gitea.example.com/${{ github.repository }}.git .
- name: .env erstellen
run: |
printf 'DB_PASSWORD=%s\n' "$DB_PASSWORD" > .env
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
- uses: homeserver/homeserver-actions/deploy@main
with:
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
ssh_host: ${{ secrets.SSH_HOST }}
destination: /opt/docker/mein-dienst/
remote_command: |
cd /opt/docker/mein-dienst && docker compose up -d
Für die meisten Dienste reicht ein einfaches docker compose up -d. Bei Gitea und dem Gitea-Runner selbst sieht das anders aus: dort deployt der laufende Runner seinen eigenen Container neu. Ein Henne-Ei-Problem: Der SSH-Job wartet darauf, dass alle gestarteten Prozesse beendet sind, aber der neue Container beendet den alten, der gerade noch läuft und auf Abschluss wartet. Die Lösung: Mit nohup docker compose up -d & startet Docker Compose im Hintergrund und der Job kann sauber abschließen.
Beim Runner kommt noch ein Detail dazu: ein kurzes sleep vor dem Neustart, damit der Runner den erfolgreichen Durchlauf noch an Gitea zurückmelden kann, bevor er sich selbst neu startet.
Ab jetzt kann ich auch Updates an Gitea oder Gitea Runner über Gitea selbst deployen. Nur wenn dabei etwas schief läuft und einer von beiden nicht mehr auf die Beine kommt muss ich nochmal selbst per SSH hand anlegen.
Die Secrets (SSH-Key, SSH-Host, Registry-Zugangsdaten) werden einmal auf Organisationsebene hinterlegt und sind dann in allen Dienst-Repos verfügbar.
Was man davon hat
Jeder neue Dienst auf dem Homeserver bekommt von Anfang an eine Pipeline. Ich erstelle ein neues Repo in Gitea, lege die deploy.yml an, und ab dem ersten Push deployt sich der Dienst selbst.
Die Konfiguration für jeden Dienst liegt in Git. Änderungen sind nachvollziehbar. Auch Rollbacks funktionieren über Git. Eine Änderung an der Konfiguration ist kurz nach einem Push ins Repo live. Und sollte mal ein Neustart eines Dientes nötig sein, lässt sich auch das durch einen Rerun des Deployments mit wenigen Klicks in der Oberfläche erreichen.
Das war genau der Grund, warum ich mich entschlossen habe, Gitea zuerst zu installieren. Alles danach wurde einfacher.