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

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

So hostest du Gitea per Docker Compose: ein leichtgewichtiger, eigener Git-Server als ressourcensparende GitHub- und GitLab-Alternative. Mit compose.yaml, Ersteinrichtung ueber die Web-UI, Push per HTTPS und SSH, Organisationen, Actions-CI und Backup.

Linux-Terminal mit einem Bash-Skript als Sinnbild für Shell-Automatisierung

Gitea ist eine leichtgewichtige, in Go geschriebene Git-Plattform zum Selbsthosten und eine ressourcensparende Alternative zu GitHub oder GitLab. Du bekommst eine vollwertige Web-Oberflaeche fuer Repositories, Issues, Pull Requests, Organisationen und sogar CI ueber Gitea Actions – und das auf Hardware, die fuer GitLab viel zu klein waere. Diese Anleitung zeigt dir Schritt fuer Schritt, wie du Gitea per Docker Compose aufsetzt: von der compose.yaml ueber die Web-Erstinstallation und das erste Repository bis zu Push per SSH und HTTPS, Rechte-Verwaltung, Update-Workflow und Backup. Sie richtet sich an Admins im Mittelstand und ambitionierte Heimserver-Nutzer, die ihren Quellcode auf dem eigenen Server behalten wollen.

Voraussetzungen

Bevor du startest, sollte folgendes vorbereitet sein:

  • Server: Linux mit Docker, z. B. Debian 12 oder Ubuntu 24.04. Gitea ist sehr genuegsam: Minimal reichen ca. 2 CPU-Kerne und 1 GB RAM; fuer mehrere aktive Nutzer plane 2+ Kerne und 2 GB RAM ein. Der Storage-Bedarf richtet sich nach der Groesse deiner Repositories.
  • GPU: Wird nicht benoetigt. Gitea ist ein reines CPU-Tool ohne KI-Workload (kein LLM-Serving, keine Bildgenerierung). Ein nvidia-container-toolkit ist hier also ueberfluessig – die CPU reicht vollkommen.
  • Docker Engine + Compose-Plugin: aktuell installiert. Wenn du mit Docker-Stacks noch nicht vertraut bist, hilft dir unsere Anleitung Docker Compose Grundlagen: Stacks sauber aufbauen.
  • Domain (empfohlen): Fuer den produktiven Betrieb solltest du eine Domain (z. B. git.deine-domain.de) und einen vorgelagerten Reverse-Proxy mit HTTPS einsetzen, statt den nackten Port 3000 ins Internet zu stellen. Im LAN-Test genuegt zunaechst die Server-IP.
  • Freier SSH-Host-Port: Der Container-SSH-Dienst wird auf einen freien Host-Port gemappt (hier 222), weil Port 22 in der Regel schon vom System-SSHD belegt ist.

Schritt 1: Docker pruefen und Arbeitsverzeichnis anlegen

Pruefe zuerst, dass Docker und das Compose-Plugin laufen, und lege ein Arbeitsverzeichnis fuer deinen Gitea-Stack an:

docker --version
docker compose version

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

In diesem Verzeichnis legst du gleich deine compose.yaml ab. Die eigentlichen Gitea-Daten – Repositories, Konfiguration, Anhaenge, LFS und (bei SQLite) die Datenbank – landen persistent unter /data im Container, das wir gleich auf ein Docker-Volume legen.

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

Fuer kleine bis mittlere Instanzen ist die eingebaute SQLite-Datenbank ideal: kein zusaetzlicher Container, weniger bewegliche Teile. Wir nutzen ausserdem ein Named Volume statt eines Bind-Mounts – damit verwaltet Docker die Datei-Berechtigungen automatisch und du laeufst nicht in Permission-Probleme. Lege folgende compose.yaml an:

networks:
  gitea:
    external: false

volumes:
  gitea:
    driver: local

services:
  server:
    image: docker.gitea.com/gitea:1.26.2
    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 dieser Datei:

  • Image: docker.gitea.com/gitea:1.26.2 ist das aktuelle Stable-Image (Release vom 2026-05-20). Hinweis: Die Registry ist von Docker Hub (gitea/gitea) auf docker.gitea.com umgezogen. Weitere Tags sind :latest, :1, :1.26 und :nightly. Bleibe innerhalb einer Anleitung konsistent bei einer Quelle.
  • Ports: 3000:3000 ist die HTTP-Web-UI. 222:22 mappt den Git-SSH-Dienst des Containers auf den freien Host-Port 222 (eine gaengige Alternative ist 2222).
  • Volume: gitea:/data haelt alle Daten persistent. Die Konfiguration landet als app.ini unter /data/gitea/conf/app.ini.
  • Zeitzone: Die beiden read-only Mounts /etc/timezone und /etc/localtime sorgen fuer korrekte Zeitstempel.
  • USER_UID/USER_GID: Bei Named Volumes unkritisch. Nutzt du spaeter einen Bind-Mount, muessen diese Werte zum Eigentuemer des Host-Ordners passen.
  • restart: always: startet Gitea nach einem Reboot automatisch wieder.

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

Fuer groessere Instanzen oder wenn du ohnehin einen Datenbank-Server betreibst, kannst du Gitea statt SQLite mit PostgreSQL oder MySQL verbinden. Die Datenbank-Verbindung konfigurierst du komplett ueber Env-Vars nach dem Schema GITEA__<section>__<KEY>. Hier eine vollstaendige compose.yaml mit PostgreSQL:

networks:
  gitea:
    external: false

services:
  server:
    image: docker.gitea.com/gitea:1.26.2
    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=DEIN-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=DEIN-DB-PASSWORT
      - POSTGRES_DB=gitea
    networks:
      - gitea
    volumes:
      - ./postgres:/var/lib/postgresql/data

Beachte: In diesem Beispiel nutzt der Server-Block einen Bind-Mount (./gitea:/data). Damit der Container startet, muss der Host-Ordner dem Benutzer mit USER_UID/USER_GID gehoeren. Wer das vermeiden will, bleibt beim Named Volume aus Schritt 2.

Moechtest du lieber MySQL statt PostgreSQL, ersetzt du den db-Block durch folgenden und passt im Server-Block GITEA__database__DB_TYPE=mysql sowie GITEA__database__HOST=db:3306 an:

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

Sicherheitshinweis: Die hier gezeigten Beispiel-Passwoerter sind nur Platzhalter. Ersetze DEIN-DB-PASSWORT und DEIN-ROOT-PASSWORT ueberall durch eigene, starke Werte – sonst hast du faktisch das aus alten Anleitungen bekannte Standardpasswort gitea/gitea im Einsatz.

Schritt 4: Stack starten und Logs pruefen

Jetzt startest du den Stack im Hintergrund (Detached) und verfolgst kurz die Logs, um zu sehen, dass Gitea sauber hochkommt:

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

Sobald in den Logs keine Fehler mehr auftauchen und Gitea auf Port 3000 lauscht, ist der Container bereit fuer die Web-Erstinstallation.

Schritt 5: Web-Erstinstallation durchfuehren

Oeffne im Browser die Installationsseite ueber die IP (oder Domain) deines Servers:

http://DEINE-SERVER-IP:3000

Du landest auf der einmaligen Installationsseite. Pruefe und ergaenze dort vor allem:

  • Datenbank-Typ: Bei der SQLite-Variante aus Schritt 2 ist hier nichts weiter noetig. Hast du PostgreSQL/MySQL gewaehlt, sind die per Env-Vars gesetzten Werte bereits vorausgefuellt.
  • SSH-Server-Port: Trage hier den nach aussen sichtbaren SSH-Port ein, also 222 (passend zum Port-Mapping). Damit Gitea anschliessend korrekte Klon-URLs ausgibt, entspricht das der Einstellung GITEA__server__SSH_PORT.
  • Gitea Base URL (ROOT_URL): Die oeffentliche Adresse deiner Instanz, z. B. https://git.deine-domain.de/. Ist dieser Wert falsch, stimmen generierte Links, Klon-URLs und Webhooks spaeter nicht.
  • Administrator-Konto: Lege ganz unten direkt das Admin-Konto an. Hinweis: Es gibt kein vordefiniertes Default-Passwort – das erste registrierte Konto wird automatisch zum Administrator.

Mit einem Klick auf Gitea installieren schreibt Gitea die Konfiguration in /data/gitea/conf/app.ini und du landest in der fertigen Oberflaeche.

Schritt 6: Erstes Repository, Push per HTTPS und SSH

Lege jetzt ueber das Plus-Symbol oben rechts dein erstes Repository an (z. B. mein-projekt, wahlweise public oder private). Fuer den Code-Transfer hast du zwei Wege.

Push per HTTPS

Der einfachste Weg fuer den Start. Statt deines Passworts solltest du einen Personal Access Token verwenden (in Gitea unter Einstellungen → Anwendungen erstellbar). Geklont und gepusht wird ganz klassisch:

git clone https://git.deine-domain.de/DEIN-USER/mein-projekt.git
cd mein-projekt
# ... Aenderungen ...
git add .
git commit -m "Erster Commit"
git push origin main

Push per SSH

Komfortabler und ohne wiederholte Passworteingabe. Hinterlege dazu zuerst deinen oeffentlichen SSH-Schluessel in Gitea unter Einstellungen → SSH-/GPG-Schluessel. Wichtig: Die Klon-URL muss den gemappten SSH-Port 222 enthalten:

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

git clone ssh://git@git.deine-domain.de:222/DEIN-USER/mein-projekt.git

Gibt Gitea SSH-URLs ohne Port oder mit falschem Port aus, ist GITEA__server__SSH_PORT bzw. der SSH-Port in den Server-Einstellungen nicht korrekt gesetzt.

Schritt 7: Organisationen, Teams und Rechte

Fuer die Zusammenarbeit im Team nutzt du Organisationen. Eine Organisation buendelt Repositories und Mitglieder; innerhalb der Organisation legst du Teams mit gestaffelten Zugriffsstufen an. So sieht das Rechte-Modell aus:

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

Typischer Aufbau im Mittelstand: eine Organisation pro Kunde oder Abteilung, darin ein Team Developer mit Write-Rechten und ein Team Lead mit Admin-Rechten. Externe Dienstleister bindest du gezielt als Collaborator an einzelnen Repos ein.

Schritt 8: Gitea Actions (CI) aktivieren

Gitea bringt mit Actions eine zu GitHub Actions kompatible CI mit. Seit Version 1.21 ist das Feature global standardmaessig aktiv (GITEA__actions__ENABLED=true), aber pro Repository musst du es einschalten – und es laeuft erst etwas, wenn ein separater Runner registriert ist. Ohne registrierten act_runner werden keine Workflows ausgefuehrt.

Hol dir zunaechst ein Registrierungs-Token aus den Admin-Einstellungen (/-/admin/actions/runners), alternativ aus den Org- oder Repo-Einstellungen. Anschliessend registrierst du einen Runner auf Basis des Images gitea/act_runner:

./act_runner register --no-interactive \
  --instance https://git.deine-domain.de \
  --token DEIN-REGISTRATION-TOKEN

# Den Runner betreibst du danach dauerhaft als eigenen Container
# (Image gitea/act_runner).

Deine Pipelines legst du als YAML-Dateien unter .gitea/workflows/*.yaml im Repository ab – die Syntax ist eng an GitHub Actions angelehnt.

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

Im produktiven Betrieb solltest du Gitea nicht ueber den nackten Port 3000 ins Internet stellen, sondern hinter einen Reverse-Proxy (Caddy, Nginx oder Traefik) mit TLS setzen. Der Proxy terminiert HTTPS und leitet intern auf Port 3000 weiter. Ein minimales Caddy-Beispiel:

git.deine-domain.de {
    reverse_proxy localhost:3000
}

Wichtig dabei: Setze GITEA__server__ROOT_URL (bzw. die ROOT_URL in der Erstinstallation) auf die oeffentliche HTTPS-URL, sonst sind generierte Links und Webhooks falsch. Bei einem vom Standard abweichenden HTTP-Port passt du zusaetzlich LOCAL_ROOT_URL in der app.ini an.

Das Update ist erfreulich simpel: neues Image ziehen und den Stack neu erstellen. Mach vorher ein Backup (siehe Schritt 10) und pruefe bei DB-Major-Upgrades kurz den Changelog:

docker compose pull && docker compose up -d

Schritt 10: Backup mit gitea dump

Gitea bringt mit gitea dump ein eingebautes Backup mit, das ein ZIP mit Repositories, app.ini, dem custom/- und data/-Verzeichnis sowie einem Datenbank-Dump erzeugt. Du fuehrst es im Container als Benutzer git aus:

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'

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

Das Dump-ZIP liegt zunaechst nur im Container unter /tmp – sichere es per docker cp auf den Host (oder einen externen Speicher), sonst geht es beim naechsten Container-Recreate verloren. Die Wiederherstellung ist manuell (kein Auto-Restore); danach fuehrst du gitea admin regenerate hooks aus, damit die Git-Hooks wieder stimmen.

Merke: Ein Backup ist erst dann ein Backup, wenn der Restore getestet wurde. Wie du das zur festen Routine machst, zeigt unsere Anleitung Backup-Restore-Test als feste Routine etablieren.

Troubleshooting

  • Container startet nicht (Bind-Mount): Bei ./gitea:/data muss der Host-Ordner dem Benutzer mit USER_UID/USER_GID (Standard 1000) gehoeren. Stimmt das nicht, bricht der Start ab. Loesung: Ordner-Eigentuemer korrigieren oder auf ein Named Volume umstellen.
  • SSH-Klonen schlaegt fehl: Host-Port 22 ist meist vom System-SSHD belegt, deshalb das Mapping auf 222/2222. Die Klon-URL muss den Port enthalten (ssh://git@host:222/...). Damit Gitea korrekte URLs ausgibt, GITEA__server__SSH_PORT auf 222 setzen.
  • Falsche Links / kaputte Webhooks: ROOT_URL zeigt nicht auf die oeffentliche URL. Bei Reverse-Proxy oder abweichendem HTTP-Port GITEA__server__ROOT_URL setzen und ggf. LOCAL_ROOT_URL in der app.ini anpassen.
  • Actions-Workflows laufen nicht: Actions sind global aktiv, aber pro Repo nicht automatisch eingeschaltet – und ohne registrierten act_runner wird gar nichts ausgefuehrt. Repo-Actions aktivieren und einen Runner registrieren.
  • Image nicht gefunden / alte Anleitung: Die Registry ist von gitea/gitea (Docker Hub) auf docker.gitea.com/gitea umgezogen. Beides funktioniert noch, aber bleibe innerhalb deines Setups konsistent bei einer Quelle und passenden Tags.
  • Wechsel Rootful/Rootless: Das normale (rootful) und das rootless Image sind nicht kompatibel zueinander – im laufenden Betrieb nicht zwischen ihnen wechseln.

Haeufige Fragen

SQLite oder doch lieber PostgreSQL/MySQL?

Fuer kleine bis mittlere Instanzen ist die eingebaute SQLite-Datenbank voellig ausreichend und spart einen zusaetzlichen Container. Erst bei vielen aktiven Nutzern oder wenn du ohnehin einen Datenbank-Server betreibst, lohnt sich PostgreSQL oder MySQL. Beide bindest du komplett ueber GITEA__database__*-Env-Vars an.

Brauche ich fuer Gitea eine GPU?

Nein. Gitea ist ein reines CPU-Tool ohne KI-Workload. Es gibt kein LLM-Serving und keine Bildgenerierung, also auch keinen Bedarf an einer NVIDIA-GPU oder dem nvidia-container-toolkit. Schon 1–2 GB RAM und ein paar CPU-Kerne genuegen.

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

Port 22 ist auf den meisten Servern bereits vom System-SSHD belegt. Deshalb mappt das Compose den Container-SSH auf den freien Host-Port 222 (oder 2222). Wichtig ist nur, dass die Klon-URL diesen Port enthaelt und GITEA__server__SSH_PORT dazu passt.

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

Deaktiviere nach Anlage des Admin-Kontos die Selbstregistrierung (DISABLE_REGISTRATION=true), aendere alle Beispiel-Passwoerter, stelle Gitea hinter einen Reverse-Proxy mit HTTPS und exponiere nicht den nackten Port 3000. Fuer Push per HTTPS nimmst du Personal Access Tokens statt des Passworts.

Wie aktualisiere ich Gitea sicher?

Mit docker compose pull && docker compose up -d ziehst du das neue Image und erstellst den Stack neu. Mach vorher ein Backup per gitea dump und wirf bei DB-Major-Upgrades einen Blick in den Changelog. Da die Daten im Volume liegen, bleiben Repos und Konfiguration erhalten.

Kann Gitea GitHub Actions ersetzen?

Fuer viele Faelle ja: Gitea Actions ist eng an die GitHub-Actions-Syntax angelehnt, Workflows liegen unter .gitea/workflows/*.yaml. Du brauchst dafuer aber einen eigenen Runner (gitea/act_runner), der die Jobs ausfuehrt – ohne ihn passiert nichts.

Fazit

Gitea ist der schnellste Weg zu einem eigenen, vollwertigen Git-Server: ressourcensparend, in wenigen Minuten per Docker Compose aufgesetzt und trotzdem mit Issues, Pull Requests, Organisationen und CI ausgestattet. Fuer den Einstieg reicht die SQLite-Variante mit Named Volume; wer waechst, haengt PostgreSQL oder MySQL ueber Env-Vars dran. Achte auf die drei haeufigsten Stolperfallen – den SSH-Port im Mapping und in SSH_PORT, eine korrekte ROOT_URL und das regelmaessige, getestete Backup per gitea dump – dann hast du eine wartungsarme, leichtgewichtige GitHub- und GitLab-Alternative vollstaendig unter eigener Kontrolle. Wer noch mehr Dienste self-hosten will, findet in unserer Kategorie Cloud weitere Anleitungen.

Weiterfuehrende Anleitungen und Quellen

Passende Anleitungen aus unserem Magazin:

Quellen und offizielle Dokumentation: