Deployn

Forgejo installieren: Eigener Git-Server mit Docker, Actions und Registry

Forgejo auf einem Debian-13-Server mit Docker und Caddy installieren, Git-Repositories hosten, Actions-Runner einrichten und die integrierte Docker Registry nutzen.

Veröffentlicht am
Forgejo installieren: Eigener Git-Server mit Docker, Actions und Registry hero image
Inhalt
Getestet mit
Forgejo 17.0.1Forgejo Runner 13.2.1Caddy 2.11Docker 29.9Debian 13
Zuletzt geprüft am

Forgejo ist ein selbst gehosteter Git-Dienst mit Weboberfläche. Du bekommst Repositories, Issues, Pull Requests, ein Wiki, CI/CD mit Forgejo Actions und eine eigene Docker Registry, alles auf einem Server, den du selbst kontrollierst. In dieser Anleitung installiere ich Forgejo auf einem Debian-Server, richte HTTPS mit Caddy ein, verbinde einen Actions-Runner und baue am Ende ein Docker-Image, das direkt in der eigenen Registry landet.

Was ist Forgejo?

Forgejo ist 2022 als Fork von Gitea entstanden und wird von der Community um den gemeinnützigen Verein Codeberg e.V. getragen. Wenn du schon meine Anleitung zu Gitea kennst, wirst du dich schnell zurechtfinden: Die Oberfläche und viele Einstellungen sind ähnlich. Ein paar Unterschiede sind das Image codeberg.org/forgejo/forgejo und der Befehl forgejo statt gitea im Container.

Das kann Forgejo ohne Zusatzsoftware:

  • Git-Repositories über HTTPS und SSH
  • Issues, Pull Requests, Meilensteine, Projekte (Kanban) und ein Wiki
  • Forgejo Actions für CI/CD, weitgehend kompatibel zu GitHub Actions
  • Eine Paketverwaltung mit Container Registry, npm, PyPI, Maven und vielen weiteren Formaten
  • Migration von GitHub, GitLab, Gitea und anderen Diensten
  • Repository-Spiegelung (Mirrors) in beide Richtungen

Glossar

Begriff Beschreibung
Forgejo Der Git-Dienst mit Weboberfläche, den wir installieren.
Forgejo Runner Ein eigenes Programm, das Workflow-Jobs ausführt. Forgejo selbst führt keine Jobs aus.
Actions Das CI/CD-System von Forgejo. Workflows liegen als YAML-Dateien im Ordner .forgejo/workflows.
Registry Ein Speicher für Docker-Images. Die Forgejo-Registry ist Teil der Paketverwaltung.
Caddy Ein Webserver, der als Reverse Proxy arbeitet und HTTPS-Zertifikate automatisch beschafft.
DinD Docker-in-Docker: ein eigener Docker-Daemon im Container, den der Runner für Jobs mit Docker-Zugriff nutzt.

Voraussetzungen

  1. Ein Server mit Debian 13. Ich habe einen VPS bei Netcup mit 2 vCPU und 4 GB RAM genutzt (Affiliate-Gutschein: Als Neukunde erhältst du mit dem Gutschein 36nc16172088180 5€ Rabatt und ich 10% als Provision). Nach der Installation inklusive Runner und Caddy belegte das System knapp 900 MB RAM. Für Forgejo allein reichen auch 2 GB, mit Actions und Docker-Builds ist mehr Platz angenehm.
  2. Eine Domain oder Subdomain, deren A-Record auf die IP deines Servers zeigt. Ich nutze hier {{domain}}.
  3. Offene Ports: 80 und 443 für Caddy sowie 2222 für Git über SSH. Der SSH-Port des Servers (22) bleibt für dich zur Verwaltung frei.

Prüfe vorher, ob der DNS-Eintrag stimmt:

Terminalbash
dig +short {{domain}}

Die Ausgabe muss die IP deines Servers sein. Wenn du noch kein SSH-Grundwissen hast, hilft dir mein Beitrag zu den VPS-Grundlagen.

Schritt 1: Docker installieren

Nach dem Login als root aktualisiere ich das System und installiere Docker mit dem offiziellen Skript:

Terminalbash
apt update && apt upgrade -y
apt install -y ca-certificates curl dnsutils git
curl -fsSL https://get.docker.com | sh

Zur Kontrolle:

Terminalbash
docker --version
docker compose version
Docker version 29.9.3, build 5457199
Docker Compose version v5.7.0

Wer die Paketquelle lieber von Hand einträgt, findet die Schritte für Debian in der Docker-Dokumentation.

Schritt 2: Forgejo und Caddy mit Docker Compose starten

Wir legen ein Verzeichnis an. Der Ordner forgejo bekommt die Nutzer-ID 1000, damit der Container hineinschreiben darf.

Terminalbash
mkdir -p /opt/forgejo/forgejo /opt/forgejo/caddy_data /opt/forgejo/caddy_config
cd /opt/forgejo
chown 1000:1000 forgejo

Dann die compose.yaml:

yaml
services:
    forgejo:
        image: codeberg.org/forgejo/forgejo:17
        container_name: forgejo
        restart: unless-stopped
        environment:
            - USER_UID=1000
            - USER_GID=1000
            - FORGEJO__server__DOMAIN={{domain}}
            - FORGEJO__server__ROOT_URL=https://{{domain}}/
            - FORGEJO__server__SSH_DOMAIN={{domain}}
            - FORGEJO__server__SSH_PORT=2222
        volumes:
            - ./forgejo:/data
            - /etc/localtime:/etc/localtime:ro
        ports:
            - "2222:22"

    caddy:
        image: caddy:2
        container_name: caddy
        restart: unless-stopped
        ports:
            - "80:80"
            - "443:443"
        volumes:
            - ./Caddyfile:/etc/caddy/Caddyfile:ro
            - ./caddy_data:/data
            - ./caddy_config:/config

Ein paar Erklärungen dazu:

  • :17 ist die Hauptversion. Der Tag folgt den Updates innerhalb von 17.x.x, springt aber nicht von selbst auf 18. Den Wechsel auf die nächste Hauptversion machst du bewusst, nachdem du die Release Notes gelesen hast. Wer weniger Aufwand mit Updates möchte, kann den LTS-Zweig nehmen (aktuell :15, Support laut Release-Plan bis Juli 2027). Version 17 läuft laut Plan Ende Januar 2027 aus.
  • Die FORGEJO__…-Variablen füllen den Installer später vor. Das Schema ist FORGEJO__ABSCHNITT__SCHLÜSSEL und entspricht den Einträgen in der app.ini.
  • Port 2222:22 veröffentlicht den SSH-Server des Containers auf Port 2222 des Hosts. Der Web-Port 3000 bleibt intern, Caddy spricht über das Docker-Netzwerk mit Forgejo.

Die Caddyfile ist kurz:

{{domain}} {
    reverse_proxy forgejo:3000
}

Starten:

Terminalbash
docker compose up -d
docker compose ps
NAME      IMAGE                             COMMAND                  SERVICE   CREATED        STATUS                  PORTS
caddy     caddy:2                           "caddy run --config …"   caddy     1 second ago   Up Less than a second   0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp, 443/udp, 2019/tcp
forgejo   codeberg.org/forgejo/forgejo:17   "/usr/bin/entrypoint…"   forgejo   1 second ago   Up Less than a second   3000/tcp, 0.0.0.0:2222->22/tcp, [::]:2222->22/tcp

Welche Version läuft, siehst du im Container:

Terminalbash
docker compose exec forgejo forgejo --version
forgejo version 17.0.1+gitea-1.22.0 (release name 17.0.1) built with GNU Make 4.4.1, go1.26.10 : bindata, timetzdata, sqlite, sqlite_unlock_notify

Caddy holt sich beim ersten Aufruf automatisch das Zertifikat von Let’s Encrypt. In meinem Lauf war es nach wenigen Sekunden da. Im Log von Caddy steht dann certificate obtained successfully.

Schritt 3: Den Installer im Browser durchlaufen

Öffne https://{{domain}}. Forgejo zeigt den Installer, bis du ihn abgeschlossen hast.

Direkt nach dem Start tauchten im Log von Forgejo Anfragen von Bots auf, die nach Dateien wie /.env und /.git/config suchten. Der Installer ist für jeden erreichbar, bis du ihn abschließt. Schließe ihn deshalb sofort nach docker compose up -d ab und lass die Seite nicht stundenlang offen.

Der Forgejo-Installer mit gewähltem SQLite3

Die Domain, die Basis-URL und der SSH-Port sind durch die Umgebungsvariablen schon gefüllt. Bei diesen Punkten musst du selbst eingreifen:

  1. Datenbanktyp: Der Installer startet mit MySQL. Wähle SQLite3. Das reicht für Privatnutzer und kleine Teams völlig aus. Wer mehr Last erwartet, nimmt PostgreSQL, wie in der Datenbank-Doku von Forgejo beschrieben.
  2. Pfad: Das Feld ist nach der Umstellung auf SQLite3 leer, obwohl es ein Pflichtfeld ist. Trage /data/gitea/forgejo.db ein. Der Ordner /data/gitea heißt auch bei Forgejo noch so.
  3. Administratorkonto: Öffne am Ende den Block „Administratorkonto-Einstellungen” und lege Benutzername, E-Mail und Passwort fest. Das ist nicht optional, solange „Registrierung deaktivieren” aktiv ist. Ohne Admin-Konto bricht der Installer mit der Meldung „Du kannst Selbst-Registrierungen nicht deaktivieren, ohne ein Administratorkonto zu erstellen.” ab. Lass die Registrierung deaktiviert, sonst kann sich jeder auf deinem Server ein Konto anlegen.

Ein Klick auf „Forgejo installieren” und nach einigen Sekunden bist du eingeloggt.

Alles Weitere, was du hier einstellst, steht danach in /data/gitea/conf/app.ini im Container, also auf dem Server unter /opt/forgejo/forgejo/gitea/conf/app.ini. Mit den FORGEJO__…-Variablen in der compose.yaml kannst du jeden Wert später überschreiben.

Schritt 4: Das erste Repository und SSH

Ein neues Repository legst du über das Plus-Menü oben rechts an. Wenn du schon Projekte woanders hast, schau dir auch den Abschnitt zur Migration weiter unten an.

Das Formular für ein neues Repository

Für Git über SSH brauchst du einen öffentlichen Schlüssel in den Einstellungen unter „SSH- / GPG-Schlüssel”. Falls du noch keinen hast, erzeugst du ihn auf deinem Rechner mit ssh-keygen -t ed25519. Den Inhalt der Datei ~/.ssh/id_ed25519.pub fügst du bei „Schlüssel hinzufügen” ein.

Hinterlegter SSH-Schlüssel in den Benutzereinstellungen

Weil der SSH-Server des Containers auf Port 2222 läuft, muss die Clone-URL den Port enthalten. Forgejo zeigt sie so an, wie sie gebraucht wird:

Terminalbash
mkdir hello-forgejo && cd hello-forgejo
git init -b main
echo "# Hello Forgejo" > README.md
git add . && git commit -m "Erster Commit"
git remote add origin ssh://git@{{domain}}:2222/jewgeni/hello-forgejo.git
git push -u origin main
Warning: Permanently added '[{{domain}}]:2222' (ED25519) to the list of known hosts.
To ssh://{{domain}}:2222/jewgeni/hello-forgejo.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

Ersetze jewgeni durch deinen Benutzernamen. Wenn du die Eingabe des Ports sparen willst, trägst du den Host mit Port 2222 in deine ~/.ssh/config ein.

Das Repository nach dem ersten Push

Issues und Pull Requests

Der übliche Ablauf funktioniert wie auf GitHub. Ich habe ein Issue „README ausbauen” angelegt, einen Branch gepusht und einen Pull Request mit „Schliesst #1” im Text erstellt. Forgejo verlinkt die Nummer automatisch. Unter dem Pull Request siehst du später auch den Status der Actions-Jobs.

Pull Request mit grünem Prüfstatus

Schritt 5: Forgejo Actions mit eigenem Runner

Forgejo führt Workflows nicht selbst aus. Dafür gibt es den Forgejo Runner, den du auf demselben oder einem anderen Server betreibst. Ich setze ihn hier auf dem gleichen Server ein, in einem eigenen Compose-Projekt.

Aus Sicherheitsgründen gehört ein Runner eigentlich auf eine eigene Maschine, denn er führt Code aus deinen Repositories aus. Auf einem privaten Server mit nur dir als Nutzer ist das vertretbar. Mehr dazu steht in der Doku zu Securing Forgejo Actions Deployments.

Runner registrieren

Der Runner braucht eine UUID und ein Token von Forgejo. Es gibt mehrere Wege. Ich nehme die Registrierung per Kommandozeile, weil sie sich einfach kopieren lässt. Zuerst erzeugen wir ein Geheimnis aus 40 Hex-Zeichen und melden damit einen globalen Runner an:

Terminalbash
mkdir -p /opt/runner/data/.cache
cd /opt/forgejo
SECRET=$(openssl rand -hex 20)
echo $SECRET > /opt/runner/secret.txt
docker compose exec -u git forgejo forgejo forgejo-cli actions register \
  --name runner-1 --scope "" --secret $SECRET
31656461-6665-3261-3866-393661366365

Das ist die UUID des Runners. Der Parameter -u git ist wichtig: Ohne ihn läuft der Befehl als root im Container und findet die Konfiguration nicht.

Alternativ legst du den Runner in der Weboberfläche unter Administratoreinstellungen → Aktionen → Runner an und kopierst UUID und Token von dort.

Runner starten

Die Datei /opt/runner/compose.yaml:

yaml
services:
    docker-in-docker:
        image: docker:dind
        container_name: docker_dind
        privileged: true
        command: ["dockerd", "-H", "tcp://0.0.0.0:2375", "--tls=false"]
        restart: unless-stopped

    runner:
        image: data.forgejo.org/forgejo/runner:13
        container_name: runner
        depends_on:
            - docker-in-docker
        environment:
            DOCKER_HOST: tcp://docker-in-docker:2375
        user: 1001:1001
        volumes:
            - ./data:/data
        restart: unless-stopped
        command: forgejo-runner daemon --config /data/runner-config.yml

Der Runner läuft nicht als root, sondern mit der ID 1001. Der Datenordner muss ihm gehören. Dann erzeugen wir die Standard-Konfiguration:

Terminalbash
cd /opt/runner
chown -R 1001:1001 data
docker run --rm data.forgejo.org/forgejo/runner:13 \
  forgejo-runner generate-config > data/runner-config.yml

In der data/runner-config.yml änderst du drei Stellen. Zuerst die Labels. Ein Label verbindet den Namen aus runs-on: im Workflow mit einem Container-Image:

yaml
runner:
    labels: ["docker:docker://node:22-trixie"]

Dann die Verbindung zu deinem Forgejo, mit der UUID aus dem vorigen Schritt und dem Inhalt von secret.txt als Token:

yaml
server:
    connections:
        forgejo:
            url: https://{{domain}}/
            uuid: 31656461-6665-3261-3866-393661366365
            token: DEIN-SECRET-AUS-SECRET.TXT

Zuletzt der Docker-Zugriff für die Jobs, den wir für den Image-Build in Schritt 6 brauchen. Ohne diese Einträge sehen die Job-Container den Docker-Daemon nicht. Sie verweisen auf den DinD-Container, nicht auf den Docker des Servers:

yaml
runner:
    envs:
        DOCKER_HOST: tcp://dind_container.docker.internal:2375

container:
    options: "--add-host=dind_container.docker.internal:host-gateway"
    docker_host: "tcp://docker-in-docker:2375"

Die Schlüssel envs, options und docker_host stehen in der generierten Datei schon an passender Stelle, meist leer oder mit Beispielwerten. Ersetze sie dort, statt Abschnitte doppelt anzulegen. Das Konzept stammt aus der Doku Utilizing Docker within Actions.

Starten und ins Log schauen:

Terminalbash
docker compose up -d
docker compose logs runner
runner  | level=info msg="Starting runner daemon"
runner  | Error: cannot ping the docker daemon. is it running? Cannot connect to the Docker daemon at tcp://docker-in-docker:2375. Is the docker daemon running?
runner  | level=info msg="Starting runner daemon"
runner  | level=info msg="runner: runner-1, with version: v13.2.1, with labels: [docker], ephemeral: false, declared successfully"
runner  | level=info msg="[poller] launched"

Die Fehlermeldung am Anfang ist normal. Der Runner startet etwas früher als der Docker-Daemon im DinD-Container und versucht es, bis die Verbindung steht. In meinem Lauf dauerte das rund zwei Sekunden.

In den Administratoreinstellungen unter Aktionen → Runner siehst du jetzt runner-1 mit dem Status „Untätig”.

Der registrierte Runner in den Administratoreinstellungen

Der erste Workflow

Im Repository legst du die Datei .forgejo/workflows/test.yaml an:

yaml
on: [push]

jobs:
    hallo:
        runs-on: docker
        steps:
            - uses: actions/checkout@v4
            - name: Hallo sagen
              run: |
                  echo "Hallo von Forgejo Actions"
                  node --version
                  ls -la

runs-on: docker passt zu dem Label aus der Runner-Konfiguration. Die Action actions/checkout@v4 ohne vollständige URL lädt Forgejo von data.forgejo.org, einem Mirror bekannter Actions, die unter freien Lizenzen stehen. Nach git push startet der Workflow.

Liste der Workflow-Läufe im Reiter Aktionen

Im Detail siehst du jeden Schritt mit seiner Ausgabe:

Log eines erfolgreichen Workflow-Laufs

Der erste Lauf braucht länger, weil der Runner das Image node:22-trixie herunterladen muss. In meinem Test waren es 17 Sekunden, spätere Läufe dauerten zwei.

Secrets, Variablen und manuelle Starts

Passwörter und Token gehören nicht in den Workflow. Du legst sie in den Repository-Einstellungen unter Aktionen → Secrets an. Nicht geheime Werte kommen unter Variablen. Ein Workflow kann außerdem von Hand und nach Zeitplan laufen:

yaml
on:
    workflow_dispatch:
    schedule:
        - cron: "0 6 * * *"

jobs:
    secrets-test:
        runs-on: docker
        steps:
            - name: Variable und Secret nutzen
              env:
                  MEIN_GEHEIMNIS: ${{ secrets.MEIN_GEHEIMNIS }}
              run: |
                  echo "Variable: ${{ vars.GRUSS }}"
                  echo "Secret hat ${#MEIN_GEHEIMNIS} Zeichen"
                  echo "Secret-Wert: $MEIN_GEHEIMNIS"
Variable: Moin
Secret hat 9 Zeichen
Secret-Wert: ***

Forgejo maskiert Secrets im Log. Ausgegeben wird nur ***. Bei meinem ersten Versuch hatte ich den Ausdruck ${{ secrets.MEIN_GEHEIMNIS != "" }} im run-Block, und der Job scheiterte ohne verwertbare Meldung. Gib Secrets deshalb als Umgebungsvariable weiter, wie oben.

Eine Namensregel: Secret-Namen dürfen nicht mit FORGEJO_, GITEA_, GITHUB_ oder einer Zahl beginnen. Bei FORGEJO_TOKEN antwortete die API mit invalid secret name. Ich nenne mein Secret deshalb REGISTRY_TOKEN.

Schritt 6: Die integrierte Docker Registry nutzen

Zu jeder Forgejo-Instanz gehört eine Container Registry. Du brauchst keinen zusätzlichen Dienst und keine eigene Konfiguration. Die Registry liegt unter derselben Domain wie Forgejo, und Caddy leitet die Anfragen genauso weiter wie alles andere.

Anmelden, pushen, pullen

Der Image-Name folgt dem Schema {domain}/{besitzer}/{image}:{tag}. Zum Anmelden brauchst du ein Zugriffstoken. Erzeuge es in den Benutzereinstellungen unter „Anwendungen” und gib ihm Lese- und Schreibrechte für Pakete. Das Passwort deines Kontos solltest du hier nicht verwenden, bei aktivierter Zwei-Faktor-Anmeldung funktioniert es auch nicht.

Terminalbash
echo "DEIN-TOKEN" | docker login {{domain}} -u jewgeni --password-stdin
docker tag alpine:3.22 {{domain}}/jewgeni/alpine:test
docker push {{domain}}/jewgeni/alpine:test
Login Succeeded
Info -> Not all multiplatform-content is present and only the available single-platform image was pushed
         sha256:2e0b81d60597f449baa56dc25d355367869d2dca4cd57e866f1e2b52af3eb55f -> sha256:dc92f017beea8e7f43263073d717f7bc011228c95d5312e440deb4d035bb4eec

Die Info-Zeile erscheint, weil Docker mit dem containerd-Image-Store nur die Plattform pusht, die lokal vorhanden ist. Für den Alltag ist das unproblematisch.

Das Image taucht im Profil unter „Pakete” auf:

Das gepushte Image in der Paketliste

Auf einem anderen Server (nach dem Login) lädst du es so:

Terminalbash
docker pull {{domain}}/jewgeni/alpine:test

Image-Name und Repository verknüpfen

Wenn der Image-Name dem Namen eines Repositories entspricht, also {{domain}}/jewgeni/hello-forgejo, ordnet Forgejo das Paket beim ersten Push automatisch diesem Repository zu. Es erscheint dann im Reiter „Pakete” des Repositories. Alternativ setzt du im Dockerfile das Label org.opencontainers.image.source mit der Repository-URL.

Images automatisch bauen und pushen

Jetzt verbinden wir Actions und Registry. Im Repository liegen ein Dockerfile und eine index.html:

dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html

Der Workflow .forgejo/workflows/docker.yaml baut das Image bei jedem Push auf main und lädt es hoch:

yaml
on:
    push:
        branches: [main]

jobs:
    image:
        runs-on: docker
        steps:
            - uses: actions/checkout@v4
            - name: Docker-CLI installieren
              run: apt-get update -qq && apt-get install -y -qq docker-cli > /dev/null
            - name: Bei der Registry anmelden
              run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login {{domain}} -u ${{ github.actor }} --password-stdin
            - name: Image bauen und pushen
              run: |
                  docker build -t {{domain}}/${{ github.repository }}:latest .
                  docker push {{domain}}/${{ github.repository }}:latest

Dazu legst du in den Repository-Einstellungen das Secret REGISTRY_TOKEN mit dem Token aus dem vorigen Abschnitt an. Das node-Image enthält keine Docker-CLI, deshalb installiert der zweite Schritt das Paket docker-cli (in Debian 13 vorhanden, darum das Label mit trixie; mit bookworm-Images habe ich das nicht getestet). Den Daemon stellt der DinD-Container bereit, den wir in Schritt 5 konfiguriert haben.

Erfolgreicher Workflow, der ein Image gebaut und gepusht hat

Der Lauf dauerte sieben Sekunden. Danach erscheint das Image direkt am Repository und in der Paketliste.

Das Paket mit Pull-Befehl, Digest und Größe

Zum Test habe ich das lokale Image gelöscht, neu gepullt und gestartet:

Terminalbash
docker rmi {{domain}}/jewgeni/hello-forgejo:latest
docker pull {{domain}}/jewgeni/hello-forgejo:latest
docker run -d --rm --name hallo -p 8081:80 {{domain}}/jewgeni/hello-forgejo:latest
curl localhost:8081
<h1>Hallo aus meiner eigenen Registry</h1>

In einer Compose-Datei verweist image: einfach auf den vollen Namen mit Domain. Auf dem Server, der das Image zieht, musst du einmal docker login ausführen, wenn das Paket nicht öffentlich ist.

Sichtbarkeit: Wann sind Images privat?

Das ist die wichtigste Falle bei der Registry. Die Sichtbarkeit eines Pakets hängt nicht am Repository, sondern am Besitzer. Ich habe mein Repository auf „privat” gestellt und das Image anschließend ohne Anmeldung gepullt. Es kam trotzdem an, weil mein Benutzerkonto öffentlich war.

Erst nachdem ich die Sichtbarkeit des Kontos auf „privat” gesetzt hatte, kam die erwartete Antwort:

Error response from daemon: failed to resolve reference "{{domain}}/jewgeni/hello-forgejo:latest": pull access denied, repository does not exist or may require authorization: authorization failed: no basic auth credentials

Die Einstellung findest du unter Benutzereinstellungen → Profil als „Profilsichtbarkeit”. Ich habe sie in meinem Test über die Admin-API gesetzt, nicht im Formular. Bei Organisationen gilt dasselbe für die Organisation. Prüfe das, bevor du interne Images in die Registry legst, und teste mit docker logout und einem frischen Pull, ob sie wirklich geschützt sind.

Weitere Funktionen, die sich lohnen

  • Migration: Unter „Repository migrieren” holt Forgejo Repositories von GitHub, GitLab, Gitea, Gogs, OneDev, GitBucket und anderen Forgejo-Instanzen. Je nach Quelle kommen auch Issues, Pull Requests, Wiki und Releases mit. Dafür brauchst du bei privaten Quellen ein Zugriffstoken.
  • Mirrors: Ein Repository kann sich regelmäßig von einer anderen Quelle aktualisieren (Pull-Mirror) oder Änderungen an GitHub schicken (Push-Mirror). Das taugt als Backup oder als Zweitkopie.
  • Wiki, Projekte, Meilensteine, Releases: Jedes Repository bringt sie mit. Projekte funktionieren als Kanban-Board.
  • Webhooks: Forgejo ruft bei Ereignissen eine URL auf, zum Beispiel für Benachrichtigungen.
  • Zwei-Faktor-Anmeldung: Aktivierst du in den Benutzereinstellungen unter „Sicherheit”. Das lohnt sich auf jedem Server, der aus dem Internet erreichbar ist.

Auswahl der Migrationsquellen

Im Administrationsbereich findest du außerdem Wartungsaufgaben wie die Garbage Collection für Repositories und die Verwaltung von Benutzern.

Wartungsoperationen im Administrationsbereich

Backup

Alle Daten liegen im Ordner /opt/forgejo/forgejo. Für ein konsistentes Archiv bringt Forgejo den Befehl dump mit:

Terminalbash
cd /opt/forgejo
docker compose exec -u git forgejo forgejo dump --file /data/backup.zip
ls -la forgejo/backup.zip
cmd/dump.go:520:dumpDatabase() [I] Dumping database...
cmd/dump.go:336:func1() [I] Adding custom configuration file from /data/gitea/conf/app.ini
cmd/dump.go:476:dumpCustom() [I] Custom dir /data/gitea is inside data dir /data/gitea, skipping
cmd/dump.go:424:runDump() [I] Finished dumping in file /data/backup.zip
-rw------- 1 1000 1000 4058950 Nov  5 20:22 forgejo/backup.zip

Das Archiv mit knapp 4 MB enthielt in meinem Test die Datenbank, die Repositories und die Konfiguration. Kopiere es regelmäßig auf einen anderen Rechner, denn eine Sicherung auf demselben Server schützt dich nicht vor einem Serverausfall. Die Schritte für den Restore stehen in der Backup-Dokumentation von Forgejo. Sichere außerdem den Ordner /opt/runner und die Inhalte von caddy_data, wenn du keine neuen Zertifikate abwarten willst.

Forgejo aktualisieren

Innerhalb der Hauptversion genügt:

Terminalbash
cd /opt/forgejo
docker compose pull
docker compose up -d

Bei einem Sprung auf die nächste Hauptversion (von 17 auf 18) ändere den Tag in der compose.yaml, lies vorher die Release Notes und die Upgrade-Anleitung und mach vorher ein Backup. Auch den Runner hältst du aktuell, indem du dort den Tag :13 anhebst.

Probleme und Lösungen

Problem Ursache und Lösung
Installer: „Du kannst Selbst-Registrierungen nicht deaktivieren …” Kein Administratorkonto angegeben. Block „Administratorkonto-Einstellungen” ausfüllen.
Installer: Pfad der SQLite-Datei fehlt /data/gitea/forgejo.db eintragen.
forgejo-Befehl im Container findet keine Konfiguration Mit -u git ausführen: docker compose exec -u git forgejo forgejo …
Runner startet in einer Schleife Anfangs normal, solange der DinD-Container nicht bereit ist. Hält es an, prüfe DOCKER_HOST im Compose-File.
Cannot connect to the Docker daemon im Workflow Die Einträge envs, options und docker_host in der Runner-Konfiguration fehlen oder sind falsch.
invalid secret name Secret-Namen dürfen nicht mit FORGEJO_ beginnen.
Git-Push per SSH schlägt fehl Port 2222 in der Clone-URL vergessen oder in der Firewall nicht freigegeben.
Image lässt sich ohne Login pullen, obwohl das Repo privat ist Die Sichtbarkeit des Besitzerkontos ist öffentlich. Konto oder Organisation auf „privat” stellen.

Fazit

Forgejo ist schnell installiert, und die Weboberfläche ist für Einsteiger schnell durchschaubar. Der eigentliche Mehrwert gegenüber einem reinen Git-Server kommt mit Actions und der Registry: Ein Push startet den Build, das fertige Image liegt in derselben Instanz und lässt sich mit einem docker pull auf jedem Server laden. Wichtig sind die Sichtbarkeit der Pakete, ein Backup außerhalb des Servers und ein Runner.

Nutzt du schon einen eigenen Git-Server und welche Funktionen setzt du ein? Schreib es gerne in die Kommentare.

FAQ

Häufige Fragen

Was ist der Unterschied zwischen Forgejo und Gitea?

Forgejo ist 2022 als Fork von Gitea entstanden und wird von einer Community um den Verein Codeberg e.V. entwickelt. Bedienung und Konfiguration ähneln sich stark. Forgejo hat ein eigenes Release-Schema mit LTS-Versionen und einen eigenen Runner für Actions.

Wie viel RAM braucht Forgejo?

Forgejo selbst läuft mit 1 bis 2 GB. In meinem Test belegten Forgejo, Caddy, Runner und der DinD-Container zusammen knapp 900 MB, bei einem Server mit 4 GB RAM. Wer häufig Docker-Images baut, sollte mehr einplanen.

Brauche ich eine Domain für Forgejo?

Für HTTPS mit Caddy ja. Ohne Domain erreichst du Forgejo nur per IP und Port, und die Container Registry funktioniert in der Praxis nur mit HTTPS oder einer entsprechenden Docker-Ausnahme für unsichere Registries. Eine Subdomain reicht.

Warum läuft Git über Port 2222 statt 22?

Der SSH-Server des Servers belegt Port 22. Forgejo bietet seinen SSH-Zugang im Container deshalb auf 2222 an. Die Clone-URL enthält den Port: ssh://git@deine-domain:2222/benutzer/repo.git.

Sind Forgejo Actions kompatibel zu GitHub Actions?

Weitgehend. Die Syntax der Workflows ist ähnlich, die Dateien liegen aber im Ordner .forgejo/workflows. Actions ohne vollständige URL werden von data.forgejo.org geladen. Nicht jede GitHub-Action funktioniert unverändert, vor allem solche mit GitHub-spezifischen Schnittstellen.

Muss der Runner auf demselben Server laufen?

Nein. Du kannst den Runner auf einem anderen Rechner betreiben, solange er die URL deiner Forgejo-Instanz erreicht. Aus Sicherheitsgründen ist eine eigene Maschine sogar besser, weil der Runner Code aus deinen Repositories ausführt.

Wie mache ich Images in der Registry privat?

Die Sichtbarkeit richtet sich nach dem Besitzer des Pakets. Ist dein Benutzerkonto oder deine Organisation öffentlich, sind auch die Images öffentlich, selbst wenn das zugehörige Repository privat ist. Stelle das Konto auf privat und prüfe mit docker logout und einem Pull, ob der Zugriff verweigert wird.

Wie sichere ich Forgejo?

Mit dem Befehl forgejo dump im Container erzeugst du ein Archiv aus Datenbank, Repositories und Konfiguration. Kopiere es auf einen anderen Rechner.