Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Cloud / Hosting 02.06.2026 · 11 min Lesezeit

Gitea mit Docker: eigener Git-Server als GitHub-Alternative

Gitea per Docker Compose selbst hosten: ein leichtgewichtiger, eigener Git-Server als ressourcensparende GitHub- und GitLab-Alternative. Mit compose.yaml, Ersteinrichtung über die Weboberfläche, Push per HTTPS und SSH, Organisationen, Actions-CI und Backup.

Geprüft am 10.10.2026 · für Gitea 28.1.0

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Kompaktes Speichergerät auf Holzregal, darüber ein bunt leuchtendes Netzwerk aus Lichtpunkten, umgeben von Pflanzen

Gitea ist eine leichtgewichtige, in Go geschriebene Git-Plattform zum Selbsthosten und eine ressourcensparende Alternative zu GitHub oder GitLab: Repositories, Issues, Pull Requests, Organisationen und CI über Gitea Actions, auf Hardware, die für GitLab zu klein wäre. Diese Anleitung zeigt das Setup per Docker Compose von der compose.yaml über Erstinstallation, erstes Repository und Push per SSH/HTTPS bis zu Rechten, Update und Backup.

Voraussetzungen

Voraussetzungen:

  • Server: Linux mit Docker, z. B. Debian 12 oder Ubuntu 24.04. Minimal ca. 2 CPU-Kerne und 1 GB RAM, für mehrere aktive Nutzer 2 GB RAM; Speicher nach Größe der Repositories.
  • GPU: nicht nötig, auch kein nvidia-container-toolkit; Gitea hat keinen KI-Workload.
  • Docker Engine + Compose-Plugin, Grundlagen: Docker Compose Grundlagen: Stacks sauber aufbauen.
  • Domain (empfohlen): produktiv eine Domain (z. B. git.example.de) mit Reverse-Proxy und HTTPS statt nacktem Port 3000; im LAN-Test genügt die Server-IP.
  • Freier SSH-Host-Port (hier 222), da Port 22 meist vom System-SSHD belegt ist.

Schritt 1: Docker prüfen und Arbeitsverzeichnis anlegen

Docker und Compose-Plugin prüfen, Arbeitsverzeichnis anlegen:

docker --version
docker compose version

sudo mkdir -p /opt/gitea
cd /opt/gitea

Hier liegt die compose.yaml. Repositories, Konfiguration, Anhänge, LFS und (bei SQLite) die Datenbank landen unter /data im Container, das auf ein Docker-Volume gelegt wird.

Verifizieren: docker compose version gibt eine Version 2.x oder neuer aus, und pwd zeigt /opt/gitea.

Schritt 2: compose.yaml anlegen (SQLite, empfohlener Einstieg)

Für kleine bis mittlere Instanzen ist SQLite ideal: kein zusätzlicher Container. Ein Named Volume statt Bind-Mount vermeidet Rechteprobleme, weil Docker die Berechtigungen verwaltet. compose.yaml:

networks:
  gitea:
    external: false

volumes:
  gitea:
    driver: local

services:
  server:
    image: docker.gitea.com/gitea:28.1.0
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    networks:
      - gitea
    volumes:
      - gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "222:22"

Die wichtigsten Punkte:

  • Image: docker.gitea.com/gitea:28.1.0 ist das aktuelle Stable-Image (Stand 10.10.2026). Seit dieser Version verzichtet Gitea auf das historische Präfix 1.: Auf 1.27 folgt 28.0.0, nicht 1.28.0. Die Registry ist von Docker Hub (gitea/gitea) auf docker.gitea.com umgezogen. Weitere Tags: :latest, :28, :28.1, :nightly. Achtung: :1 bleibt auf der alten 1.27-Reihe stehen und erhält das neue Major-Release nicht. Eine feste Version macht Updates planbar.
  • Ports: 3000:3000 ist die Web-UI, 222:22 mappt Git-SSH auf Host-Port 222 (gängig auch 2222).
  • Volume: gitea:/data; die Konfiguration liegt unter /data/gitea/conf/app.ini.
  • Zeitzone: read-only Mounts /etc/timezone und /etc/localtime.
  • USER_UID/USER_GID: bei Named Volumes unkritisch, bei Bind-Mounts passend zum Eigentümer des Host-Ordners.
  • restart: always: Neustart nach Reboot.

Verifizieren: docker compose config gibt die Konfiguration ohne Fehlermeldung aus.

Schritt 3: Optional eine externe Datenbank (MySQL/PostgreSQL) anbinden

Für größere Instanzen oder einen vorhandenen DB-Server verbinden Sie Gitea mit PostgreSQL oder MySQL, komplett über Env-Vars nach dem Schema GITEA__<section>__<KEY>. compose.yaml mit PostgreSQL:

networks:
  gitea:
    external: false

services:
  server:
    image: docker.gitea.com/gitea:28.1.0
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=DB-PASSWORT
    restart: always
    networks:
      - gitea
    volumes:
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "222:22"
    depends_on:
      - db

  db:
    image: docker.io/library/postgres:14
    restart: always
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=DB-PASSWORT
      - POSTGRES_DB=gitea
    networks:
      - gitea
    volumes:
      - ./postgres:/var/lib/postgresql/data

Achtung: Hier nutzt der Server-Block einen Bind-Mount (./gitea:/data); der Host-Ordner muss USER_UID/USER_GID gehören. Alternativ beim Named Volume aus Schritt 2 bleiben.

Für MySQL ersetzen Sie den db-Block und setzen im Server-Block GITEA__database__DB_TYPE=mysql sowie GITEA__database__HOST=db:3306:

  db:
    image: docker.io/library/mysql:8
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=ROOT-PASSWORT
      - MYSQL_USER=gitea
      - MYSQL_PASSWORD=DB-PASSWORT
      - MYSQL_DATABASE=gitea
    networks:
      - gitea
    volumes:
      - ./mysql:/var/lib/mysql

Sicherheitshinweis: Ersetzen Sie die Platzhalter DB-PASSWORT und ROOT-PASSWORT überall durch eigene, starke Werte. Die offizielle Beispieldatei nutzt gitea/gitea, was im Betrieb nicht stehen bleiben darf.

Verifizieren: Nach dem Start in Schritt 4 zeigt docker compose ps auch den Dienst db als running, und die Gitea-Logs enthalten keine Verbindungsfehler zur Datenbank.

Schritt 4: Stack starten und Logs prüfen

Stack starten, Logs prüfen:

docker compose up -d        # Container starten (Detached)
docker compose logs -f      # Logs verfolgen (mit Strg+C verlassen)

# Stoppen/Entfernen (Volumes bleiben erhalten):
# docker compose down

Ohne Fehler in den Logs und mit Gitea auf Port 3000 ist der Container bereit für die Erstinstallation.

Verifizieren: docker compose ps zeigt gitea als running, und curl -sI http://localhost:3000 antwortet mit HTTP/1.1 200 OK.

Schritt 5: Web-Erstinstallation durchführen

Installationsseite über IP oder Domain öffnen:

http://SERVER-IP:3000

Prüfen und ergänzen Sie dort vor allem:

  • Datenbank-Typ: bei SQLite nichts zu tun; bei PostgreSQL/MySQL aus den Env-Vars vorausgefüllt.
  • SSH-Server-Port: den äußeren Port 222 eintragen (entspricht GITEA__server__SSH_PORT), damit die Klon-URLs stimmen.
  • Gitea Base URL (ROOT_URL): öffentliche Adresse, z. B. https://git.example.de/; sonst stimmen Links, Klon-URLs und Webhooks nicht.
  • Administrator-Konto: unten direkt anlegen. Es gibt kein Default-Passwort; das erste registrierte Konto wird Administrator.

Gitea installieren schreibt /data/gitea/conf/app.ini und leitet zur Oberfläche weiter.

Verifizieren: Die Anmeldung mit dem neuen Admin-Konto funktioniert, und im Menü erscheint der Eintrag Administration.

Schritt 6: Erstes Repository, Push per HTTPS und SSH

Legen Sie über das Plus-Symbol oben rechts das erste Repository an (z. B. mein-projekt, public oder private).

Push per HTTPS

Der einfachste Weg. Statt des Passworts einen Personal Access Token nutzen (Einstellungen → Anwendungen):

git clone https://git.example.de/BENUTZER/mein-projekt.git
cd mein-projekt
# ... Änderungen ...
git add .
git commit -m "Erster Commit"
git push origin main

Push per SSH

Ohne wiederholte Passworteingabe. Öffentlichen Schlüssel unter Einstellungen → SSH-/GPG-Schlüssel hinterlegen. Die Klon-URL muss Port 222 enthalten:

# Falls noch kein Key existiert:
ssh-keygen -t ed25519 -C "name@example.de"
# Inhalt von ~/.ssh/id_ed25519.pub in Gitea hinterlegen, dann:

git clone ssh://git@git.example.de:222/BENUTZER/mein-projekt.git

Zeigt Gitea SSH-URLs ohne oder mit falschem Port, ist GITEA__server__SSH_PORT bzw. der SSH-Port in den Server-Einstellungen falsch.

Verifizieren: ssh -T -p 222 git@git.example.de antwortet mit einer Begrüßung, die Ihren Gitea-Benutzernamen nennt, und nach dem Push erscheint der Commit in der Weboberfläche des Repositorys.

Schritt 7: Organisationen, Teams und Rechte

Organisationen bündeln Repositories und Mitglieder; darin gibt es Teams mit gestaffelten Zugriffsstufen:

EbeneFunktion
OrganisationKlammert Repositories und Mitglieder; Repos sind je nach Bedarf public oder private
TeamBündelt Mitglieder mit einer Zugriffsstufe (Read / Write / Admin) und Zuweisung zu Repos bzw. Einheiten (Units)
CollaboratorEinzelner Mitarbeiter, der pro Repository direkt zugewiesen wird – ohne über ein Team zu gehen

Typisch: eine Organisation pro Kunde oder Abteilung, Team Developer mit Write-, Team Lead mit Admin-Rechten; externe Dienstleister als Collaborator an einzelnen Repos.

Verifizieren: Ein Testkonto im Team Developer kann in ein Repository der Organisation pushen, aber die Repository-Einstellungen nicht öffnen.

Schritt 8: Gitea Actions (CI) aktivieren

Actions ist eine GitHub-Actions-kompatible CI. Seit 1.21 global standardmäßig aktiv (GITEA__actions__ENABLED=true), muss aber pro Repository eingeschaltet werden; ohne registrierten Runner laufen keine Workflows.

Registrierungs-Token aus den Admin-Einstellungen (/-/admin/actions/runners) oder Org-/Repo-Einstellungen holen. Der Runner heißt inzwischen Gitea Runner (früher act_runner), Image gitea/runner; als Container registriert er sich beim ersten Start selbst:

docker run -d --name gitea-runner --restart always \
  -e GITEA_INSTANCE_URL=https://git.example.de \
  -e GITEA_RUNNER_REGISTRATION_TOKEN=REGISTRIERUNGS-TOKEN \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v gitea-runner-data:/data \
  gitea/runner:4

Der Runner erhält Zugriff auf den Docker-Socket und kann beliebige Container starten; die Gitea-Doku empfiehlt deshalb eine eigene Maschine. GITEA_INSTANCE_URL darf keine Loopback-Adresse wie localhost sein, da Job-Container sie nicht erreichen. Pipelines liegen unter .gitea/workflows/*.yaml.

Verifizieren: Unter /-/admin/actions/runners erscheint der Runner mit Status Idle, und ein einfacher Test-Workflow läuft nach einem Push durch.

Schritt 9: Reverse-Proxy, HTTPS und Update-Workflow

Produktiv gehört Gitea hinter einen Reverse-Proxy (Caddy, Nginx oder Traefik), der HTTPS terminiert und intern auf Port 3000 weiterleitet. Minimales Caddy-Beispiel:

git.example.de {
    reverse_proxy localhost:3000
}

Wichtig: GITEA__server__ROOT_URL (bzw. ROOT_URL der Erstinstallation) auf die öffentliche HTTPS-URL setzen, sonst sind Links und Webhooks falsch. Bei abweichendem HTTP-Port zusätzlich LOCAL_ROOT_URL in der app.ini anpassen.

Update: neues Image ziehen, Stack neu erstellen. Vorher Backup (Schritt 10) und Changelog, denn bei Sprüngen wie 1.27 auf 28.0 migriert Gitea die Datenbank. Bei festem Tag zuerst die Version in der compose.yaml anheben:

docker compose pull && docker compose up -d

Von 1.27 auf 28.0: Zwei Breaking Changes betreffen Bestandsinstallationen. Erstens löscht Gitea abgeschlossene Actions-Läufe jetzt nach RUN_RETENTION_DAYS (Standard 400 Tage) beim nächsten nächtlichen Cleanup; wer die Historie behalten will, setzt vor dem Update GITEA__actions__RUN_RETENTION_DAYS=0. Der Wert 0 bedeutet seit 28.0 auch bei LOG_RETENTION_DAYS und ARTIFACT_RETENTION_DAYS „unbegrenzt“. Zweitens laufen Git-Netzwerkzugriffe (Migrationen, Spiegel) über einen internen Proxy mit neuen Egress-Regeln: Im Standardmodus lax schränkt [security] ALLOWED_HOST_LIST öffentliche Ziele nicht mehr ein, für eine exklusive Liste ist EGRESS_MODE = strict nötig. Platzhalter in IP-Adressen und * als Listeneintrag sind nicht mehr erlaubt, ungültige Einträge in BLOCKED_HOST_LIST verhindern den Start. Wer diese Listen nicht gesetzt hat, muss nichts tun. Im Test lief das Update von 1.27.3 auf 28.0.0 mit SQLite ohne Eingriff durch, die Datenbankmigrationen erscheinen beim ersten Start im Log (docker compose logs | grep Migration).

Von 28.0 auf 28.1: ein reines Fehlerbehebungs-Update ohne Breaking Changes. Für MySQL und MariaDB arbeitet Gitea jetzt mit Transaktionen im Modus READ COMMITTED, dazu kommen Korrekturen an Actions, npm-Paketen und der API. Im Test lief das Update von 28.0.0 auf 28.1.0 ohne Eingriff: Gitea spielte zwei Migrationen ein (Index auf action_run, Bereinigung alter Team-Berechtigungen), ein vorher angelegtes Repository blieb erhalten und ließ sich per HTTP abrufen.

Verifizieren: curl -sI https://git.example.de liefert 200 mit gültigem Zertifikat, und nach einem Update zeigt die Fußzeile der Weboberfläche die neue Versionsnummer.

Schritt 10: Backup mit gitea dump

gitea dump erzeugt ein ZIP mit Repositories, app.ini, custom/, data/ und Datenbank-Dump. Es läuft im Container als Benutzer git:

docker exec -u git -it -w /tmp $(docker ps -qf 'name=^gitea$') \
  bash -c '/usr/local/bin/gitea dump -c /data/gitea/conf/app.ini'

# Anschließend das erzeugte ZIP aus /tmp auf den Host kopieren:
docker cp gitea:/tmp/gitea-dump-XXXXXXXXXX.zip ./

Das ZIP liegt nur im Container unter /tmp: per docker cp auf den Host oder externen Speicher sichern, sonst geht es beim nächsten Recreate verloren. Die Wiederherstellung ist manuell; danach stellt gitea admin regenerate hooks die Git-Hooks wieder her.

Verifizieren: unzip -l gitea-dump-*.zip listet unter anderem app.ini, gitea-db.sql und das Verzeichnis repos/.

Merke: Ein Backup ist erst mit getestetem Restore eines: Backup-Restore-Test als feste Routine etablieren.

Troubleshooting

  • Container startet nicht (Bind-Mount): Bei ./gitea:/data muss der Host-Ordner USER_UID/USER_GID (Standard 1000) gehören. Eigentümer korrigieren oder Named Volume nutzen.
  • SSH-Klonen schlägt fehl: Klon-URL muss den Port enthalten (ssh://git@host:222/...), GITEA__server__SSH_PORT auf 222 setzen.
  • Falsche Links / kaputte Webhooks: ROOT_URL falsch; siehe Schritt 9.
  • Actions-Workflows laufen nicht: pro Repo nicht eingeschaltet oder kein Runner registriert (Schritt 8).
  • Image nicht gefunden / alte Anleitung: Registry ist von gitea/gitea (Docker Hub) auf docker.gitea.com/gitea umgezogen. Beides funktioniert, bleiben Sie aber innerhalb eines Setups bei einer Quelle und passenden Tags.
  • Wechsel Rootful/Rootless: Beide Images sind nicht kompatibel; im laufenden Betrieb nicht wechseln.

Häufige Fragen

SQLite oder doch lieber PostgreSQL/MySQL?

SQLite reicht für kleine bis mittlere Instanzen. PostgreSQL oder MySQL lohnen erst bei vielen aktiven Nutzern oder vorhandenem DB-Server (Schritt 3).

Brauche ich für Gitea eine GPU?

Nein, siehe Voraussetzungen: 1–2 GB RAM und ein paar CPU-Kerne genügen.

Warum landet der SSH-Zugriff auf Port 222 statt 22?

Port 22 belegt meist der System-SSHD. Klon-URL und GITEA__server__SSH_PORT müssen zum Host-Port 222 (oder 2222) passen.

Wie sichere ich Gitea ab, bevor es ins Internet geht?

Nach Anlage des Admin-Kontos Selbstregistrierung abschalten (GITEA__service__DISABLE_REGISTRATION=true), Beispiel-Passwörter ändern, Reverse-Proxy mit HTTPS, Port 3000 nicht direkt ins Internet; für HTTPS-Push Personal Access Tokens statt Passwort.

Wie aktualisiere ich Gitea sicher?

docker compose pull && docker compose up -d, vorher gitea dump und Changelog (Schritt 9). Die Daten im Volume bleiben erhalten.

Kann Gitea GitHub Actions ersetzen?

Für viele Fälle ja: Die Syntax ist eng an GitHub Actions angelehnt, Workflows liegen unter .gitea/workflows/*.yaml. Nötig ist ein eigener Runner (gitea/runner).

Fazit

Gitea ist ein schneller Weg zum eigenen Git-Server mit Issues, Pull Requests, Organisationen und CI. Für den Einstieg reicht SQLite mit Named Volume; PostgreSQL oder MySQL hängen Sie später über Env-Vars an. Die häufigsten Stolperfallen: SSH-Port im Mapping und in SSH_PORT, korrekte ROOT_URL und regelmäßiges, getestetes Backup per gitea dump. Weitere Anleitungen finden Sie in der Kategorie Cloud.

Weiterführende Anleitungen und Quellen

Passende Anleitungen:

Quellen und offizielle Dokumentation: