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.

Inhalt
Forgejo 17.0.1Forgejo Runner 13.2.1Caddy 2.11Docker 29.9Debian 13Forgejo 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
- 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
36nc161720881805€ 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. - Eine Domain oder Subdomain, deren A-Record auf die IP deines Servers zeigt. Ich nutze hier
{{domain}}. - 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:
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:
apt update && apt upgrade -y
apt install -y ca-certificates curl dnsutils git
curl -fsSL https://get.docker.com | shZur Kontrolle:
docker --version
docker compose versionDocker version 29.9.3, build 5457199
Docker Compose version v5.7.0Wer 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.
mkdir -p /opt/forgejo/forgejo /opt/forgejo/caddy_data /opt/forgejo/caddy_config
cd /opt/forgejo
chown 1000:1000 forgejoDann die compose.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:/configEin paar Erklärungen dazu:
:17ist 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 istFORGEJO__ABSCHNITT__SCHLÜSSELund entspricht den Einträgen in derapp.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:
docker compose up -d
docker compose psNAME 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/tcpWelche Version läuft, siehst du im Container:
docker compose exec forgejo forgejo --versionforgejo 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_notifyCaddy 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
/.envund/.git/configsuchten. Der Installer ist für jeden erreichbar, bis du ihn abschließt. Schließe ihn deshalb sofort nachdocker compose up -dab und lass die Seite nicht stundenlang offen.

Die Domain, die Basis-URL und der SSH-Port sind durch die Umgebungsvariablen schon gefüllt. Bei diesen Punkten musst du selbst eingreifen:
- 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.
- Pfad: Das Feld ist nach der Umstellung auf SQLite3 leer, obwohl es ein Pflichtfeld ist. Trage
/data/gitea/forgejo.dbein. Der Ordner/data/giteaheißt auch bei Forgejo noch so. - 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.

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.

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:
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 mainWarning: 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.

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.

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:
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 $SECRET31656461-6665-3261-3866-393661366365Das 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:
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.ymlDer Runner läuft nicht als root, sondern mit der ID 1001. Der Datenordner muss ihm gehören. Dann erzeugen wir die Standard-Konfiguration:
cd /opt/runner
chown -R 1001:1001 data
docker run --rm data.forgejo.org/forgejo/runner:13 \
forgejo-runner generate-config > data/runner-config.ymlIn 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:
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:
server:
connections:
forgejo:
url: https://{{domain}}/
uuid: 31656461-6665-3261-3866-393661366365
token: DEIN-SECRET-AUS-SECRET.TXTZuletzt 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:
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:
docker compose up -d
docker compose logs runnerrunner | 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 erste Workflow
Im Repository legst du die Datei .forgejo/workflows/test.yaml an:
on: [push]
jobs:
hallo:
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Hallo sagen
run: |
echo "Hallo von Forgejo Actions"
node --version
ls -laruns-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.

Im Detail siehst du jeden Schritt mit seiner Ausgabe:

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:
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.
echo "DEIN-TOKEN" | docker login {{domain}} -u jewgeni --password-stdin
docker tag alpine:3.22 {{domain}}/jewgeni/alpine:test
docker push {{domain}}/jewgeni/alpine:testLogin Succeeded
Info -> Not all multiplatform-content is present and only the available single-platform image was pushed
sha256:2e0b81d60597f449baa56dc25d355367869d2dca4cd57e866f1e2b52af3eb55f -> sha256:dc92f017beea8e7f43263073d717f7bc011228c95d5312e440deb4d035bb4eecDie 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:

Auf einem anderen Server (nach dem Login) lädst du es so:
docker pull {{domain}}/jewgeni/alpine:testImage-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:
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.htmlDer Workflow .forgejo/workflows/docker.yaml baut das Image bei jedem Push auf main und lädt es hoch:
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 }}:latestDazu 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.

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

Zum Test habe ich das lokale Image gelöscht, neu gepullt und gestartet:
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 credentialsDie 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.

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

Backup
Alle Daten liegen im Ordner /opt/forgejo/forgejo. Für ein konsistentes Archiv bringt Forgejo den Befehl dump mit:
cd /opt/forgejo
docker compose exec -u git forgejo forgejo dump --file /data/backup.zip
ls -la forgejo/backup.zipcmd/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.zipDas 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:
cd /opt/forgejo
docker compose pull
docker compose up -dBei 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.