Gitea auf einem VPS mit Docker Compose selbst hosten

10 Min. Lesezeit·Matthieu·ci-cdself-hostingdocker-composegiteapostgresqldockergit|

Richten Sie eine produktionsreife Gitea-Instanz mit PostgreSQL, SSH-Passthrough auf Port 22, Gitea Actions CI/CD, Git LFS, GitHub-Mirroring und automatisierten Backups ein. Alles auf einem einzigen VPS mit Docker Compose.

Gitea ist ein leichtgewichtiger, selbst gehosteter Git-Dienst, geschrieben in Go. Er bietet Pull Requests, Issue-Tracking, CI/CD, Paketregistrierungen und Git LFS in einer einzigen Binary, die im Leerlauf etwa 150 MB RAM verbraucht. Vergleichen Sie das mit den 4 GB Minimum von GitLab und Sie verstehen, warum Gitea perfekt auf einen VPS passt.

Diese Anleitung richtet Gitea mit Docker Compose und PostgreSQL auf einem VPS ein. Sie konfigurieren SSH-Passthrough, damit git clone git@ihr-server:user/repo.git auf Port 22 funktioniert, richten Gitea Actions mit einem containerisierten Runner ein, aktivieren Git LFS, spiegeln Repositories von GitHub, konfigurieren Webhooks und automatisieren Backups.

Was brauchen Sie vor der Installation von Gitea?

Sie benötigen einen VPS mit Debian 12 oder Ubuntu 24.04, auf dem Docker und Docker Compose v2 installiert sind, einen Domainnamen, der auf Ihren Server zeigt, und einen Reverse Proxy (Nginx oder Caddy) mit konfiguriertem TLS. Diese Anleitung setzt voraus, dass Sie die grundlegende Serverhärtung durchgeführt haben: Nicht-Root-Benutzer mit sudo, SSH-Schlüsselauthentifizierung, Firewall aktiviert.

Voraussetzung Minimum
Betriebssystem Debian 12 / Ubuntu 24.04
RAM 1 GB (2 GB empfohlen mit CI-Runner)
Docker 27.x+ mit Compose v2
Domain A-Record, der auf die VPS-IP zeigt
Reverse Proxy Nginx oder Caddy mit TLS

Wie richten Sie Gitea mit Docker Compose und PostgreSQL ein?

Erstellen Sie ein Projektverzeichnis, generieren Sie Secrets in einer .env-Datei und definieren Sie die Gitea- und PostgreSQL-Dienste in docker-compose.yml. Die .env-Datei hält Zugangsdaten aus der Versionskontrolle und den Compose-Dateien heraus.

Projektverzeichnis erstellen

sudo mkdir -p /opt/gitea
sudo chown $USER:$USER /opt/gitea
cd /opt/gitea

Secrets generieren

openssl rand -base64 32 > /dev/null  # test that openssl works
cat > .env << 'ENVFILE'
POSTGRES_USER=gitea
POSTGRES_PASSWORD=REPLACE_ME
POSTGRES_DB=gitea
GITEA_SECRET_KEY=REPLACE_ME
GITEA_INTERNAL_TOKEN=REPLACE_ME
GITEA_LFS_JWT_SECRET=REPLACE_ME
ENVFILE

Ersetzen Sie nun jedes REPLACE_ME durch ein echtes Secret:

sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$(openssl rand -base64 32)/" .env
sed -i "s/^GITEA_SECRET_KEY=.*/GITEA_SECRET_KEY=$(openssl rand -base64 32)/" .env
sed -i "s/^GITEA_INTERNAL_TOKEN=.*/GITEA_INTERNAL_TOKEN=$(openssl rand -base64 32)/" .env
sed -i "s/^GITEA_LFS_JWT_SECRET=.*/GITEA_LFS_JWT_SECRET=$(openssl rand -base64 32)/" .env
chmod 600 .env

Docker-Compose-Datei

# /opt/gitea/docker-compose.yml
services:
  gitea:
    image: docker.gitea.com/gitea:1.25.5
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=${POSTGRES_DB}
      - GITEA__database__USER=${POSTGRES_USER}
      - GITEA__database__PASSWD=${POSTGRES_PASSWORD}
      - GITEA__server__DOMAIN=git.example.com
      - GITEA__server__ROOT_URL=https://git.example.com/
      - GITEA__server__SSH_DOMAIN=git.example.com
      - GITEA__server__SSH_PORT=22
      - GITEA__server__LFS_START_SERVER=true
      - GITEA__server__LFS_JWT_SECRET=${GITEA_LFS_JWT_SECRET}
      - GITEA__service__DISABLE_REGISTRATION=true
      - GITEA__service__REQUIRE_SIGNIN_VIEW=false
      - GITEA__security__SECRET_KEY=${GITEA_SECRET_KEY}
      - GITEA__security__INTERNAL_TOKEN=${GITEA_INTERNAL_TOKEN}
      - GITEA__actions__ENABLED=true
    restart: always
    volumes:
      - gitea-data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000"
    depends_on:
      db:
        condition: service_healthy
    networks:
      - gitea

  db:
    image: docker.io/library/postgres:17
    container_name: gitea-db
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    restart: always
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - gitea
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

networks:
  gitea:
    external: false

volumes:
  gitea-data:
  postgres-data:

Ersetzen Sie git.example.com durch Ihre tatsächliche Domain. Die wichtigsten Entscheidungen:

  • PostgreSQL 17 statt des veralteten 14, den die meisten Anleitungen noch empfehlen
  • Benannte Volumes (gitea-data, postgres-data) statt Bind Mounts für sauberere Verwaltung
  • Health Check auf PostgreSQL, damit Gitea wartet, bis die Datenbank bereit ist
  • DISABLE_REGISTRATION=true, da offene Registrierung auf einer öffentlichen Instanz Missbrauch einlädt
  • ACTIONS__ENABLED=true, um Gitea Actions von Anfang an zu aktivieren
  • Port 3000 an localhost gebunden (127.0.0.1:3000:3000), damit er nur über den Reverse Proxy erreichbar ist, nicht direkt aus dem Internet
  • Kein SSH-Port-Mapping am Container. SSH wird über Host-Passthrough gehandhabt (nächster Abschnitt)

Stack starten

docker compose up -d
[+] Running 3/3
 ✔ Network gitea_gitea     Created
 ✔ Container gitea-db      Healthy
 ✔ Container gitea         Started
docker compose ps
NAME        IMAGE                           STATUS                   PORTS
gitea       docker.gitea.com/gitea:1.25.5   Up 2 minutes             127.0.0.1:3000->3000/tcp
gitea-db    postgres:17                     Up 2 minutes (healthy)   5432/tcp

Reverse-Proxy-Konfiguration

Fügen Sie Gitea zu Ihrer bestehenden Nginx-Konfiguration hinzu:

server {
    listen 443 ssl http2;
    server_name git.example.com;

    ssl_certificate /etc/letsencrypt/live/git.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/git.example.com/privkey.pem;

    server_tokens off;

    client_max_body_size 512M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

client_max_body_size 512M erlaubt große Pushes, besonders nützlich bei aktivem Git LFS. server_tokens off verbirgt die Nginx-Version in den Antwort-Headern, da Versionsinformationen Angreifern helfen, bekannte Schwachstellen auszunutzen.

sudo nginx -t && sudo systemctl reload nginx

Admin-Benutzer erstellen

docker exec -it gitea gitea admin user create \
  --username admin \
  --password "$(openssl rand -base64 16)" \
  --email admin@example.com \
  --admin \
  --must-change-password
New user 'admin' has been successfully created!

Speichern Sie das generierte Passwort an einem sicheren Ort (ein Passwortmanager, kein Notizzettel). Das Flag --must-change-password erzwingt eine Passwortänderung beim ersten Login. Aktivieren Sie nach dem Einloggen die Zwei-Faktor-Authentifizierung unter Settings > Security > Two-Factor Authentication.

Wie richten Sie SSH-Passthrough für Gitea in Docker ein?

Erstellen Sie einen git-Benutzer auf dem Host mit der gleichen UID wie im Container. Fügen Sie einen AuthorizedKeysCommand-Block in /etc/ssh/sshd_config hinzu, der docker exec gitea /usr/local/bin/gitea keys aufruft. Dies leitet SSH-Git-Operationen von Port 22 auf dem Host direkt in den Container weiter, ohne einen zweiten SSH-Port freizugeben.

Git-Benutzer auf dem Host erstellen

Der Benutzer benötigt UID 1000, um mit dem internen Benutzer des Containers übereinzustimmen:

sudo adduser --system --shell /bin/bash --group --disabled-password --home /home/git --uid 1000 git

Falls UID 1000 bereits von Ihrem regulären Benutzer belegt ist, ändern Sie entweder USER_UID/USER_GID in der Compose-Datei auf eine verfügbare UID oder passen Sie das --uid-Flag hier an.

Shell-Wrapper erstellen

Die Shell des Git-Benutzers muss Befehle in den Container weiterleiten:

sudo tee /usr/local/bin/gitea-shell > /dev/null << 'WRAPPER'
#!/bin/sh
/usr/bin/docker exec -i --env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" gitea sh "$@"
WRAPPER
sudo chmod 755 /usr/local/bin/gitea-shell
sudo usermod -s /usr/local/bin/gitea-shell git
ls -la /usr/local/bin/gitea-shell
-rwxr-xr-x 1 root root 99 Mar 20 10:00 /usr/local/bin/gitea-shell

Git-Benutzer zur Docker-Gruppe hinzufügen

sudo usermod -aG docker git

Das gibt dem git-Benutzer die Berechtigung, docker exec auszuführen. Auf einem dedizierten Gitea-Server ist das akzeptabel. Auf einem geteilten Host sollten Sie stattdessen sudo-Regeln verwenden, die auf den spezifischen docker exec-Befehl beschränkt sind.

sshd konfigurieren

Fügen Sie diesen Block am Ende von /etc/ssh/sshd_config hinzu:

# Gitea SSH passthrough
Match User git
  AuthorizedKeysCommandUser git
  AuthorizedKeysCommand /usr/bin/docker exec -i gitea /usr/local/bin/gitea keys -c /etc/gitea/app.ini -e git -u %u -t %t -k %k

Der AuthorizedKeysCommand fragt den Gitea-Container, ob der vom SSH-Client präsentierte öffentliche Schlüssel zu einem registrierten Gitea-Benutzer gehört. Wenn er übereinstimmt, gewährt SSH den Zugang. Der Match User git-Block beschränkt dies auf den git-Benutzer und lässt Ihren regulären SSH-Zugang unberührt.

sudo sshd -t

Keine Ausgabe bedeutet, dass die Konfiguration gültig ist. Falls Sie Fehler sehen, prüfen Sie den Match-Block auf Tippfehler.

sudo systemctl restart sshd

SSH-Zugang testen

Fügen Sie Ihren öffentlichen SSH-Schlüssel über die Web-Oberfläche zu Ihrem Gitea-Konto hinzu (Settings > SSH/GPG Keys). Dann von Ihrem lokalen Rechner:

ssh -T git@git.example.com
Hi there, admin! You've successfully authenticated with the key named "my-laptop", but Gitea does not provide shell access.

Sie können jetzt über SSH auf Port 22 klonen, pushen und pullen:

git clone git@git.example.com:admin/my-repo.git

Wie aktivieren und konfigurieren Sie Gitea Actions für CI/CD?

Gitea Actions ist ein integriertes CI/CD-System mit einer Syntax, die mit GitHub Actions kompatibel ist. Sie aktivieren es mit einer Umgebungsvariable (bereits in unserer Compose-Datei gesetzt), stellen einen Runner als Docker-Compose-Dienst bereit und schreiben Workflow-YAML-Dateien in Ihren Repositories.

Wie registrieren Sie einen act_runner mit Docker Compose?

Generieren Sie zunächst ein Runner-Registrierungstoken über das Gitea-Admin-Panel:

docker exec -it gitea gitea actions generate-runner-token
NxxxxxxxxxxxxxxxxxxxxxxxN

Kopieren Sie dieses Token. Fügen Sie es Ihrer .env-Datei hinzu:

echo "GITEA_RUNNER_TOKEN=YOUR_TOKEN_HERE" >> /opt/gitea/.env
chmod 600 /opt/gitea/.env

Fügen Sie nun den Runner-Dienst zu Ihrer docker-compose.yml hinzu:

  runner:
    image: docker.io/gitea/act_runner:0.3.0
    container_name: gitea-runner
    environment:
      - GITEA_INSTANCE_URL=http://gitea:3000
      - GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_TOKEN}
      - GITEA_RUNNER_NAME=vps-runner
    volumes:
      - runner-data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      - gitea
    restart: always
    networks:
      - gitea

Fügen Sie runner-data: zur volumes:-Sektion am Ende der Datei hinzu. Starten Sie dann den Runner:

docker compose up -d runner
docker compose logs runner --tail 20
level=info msg="Starting runner daemon"
level=info msg="Runner registered successfully"

Der Runner mountet den Docker-Socket (/var/run/docker.sock), damit er für jeden Job Container starten kann. Das ist Standardpraxis für CI-Runner, bedeutet aber, dass Jobs auf den Docker-Daemon des Hosts zugreifen können. Für isolierte Umgebungen sollten Sie den Runner mit Docker-in-Docker (DinD) betreiben.

Umgebungsvariable Zweck
GITEA_INSTANCE_URL Interne URL, über die der Runner Gitea erreicht (verwenden Sie den Dienstnamen, nicht die öffentliche Domain)
GITEA_RUNNER_REGISTRATION_TOKEN Einmaliges Token von gitea actions generate-runner-token
GITEA_RUNNER_NAME Anzeigename im Gitea-Admin-Panel

Wie sieht ein Gitea-Actions-Workflow aus?

Gitea Actions verwendet die gleiche YAML-Syntax wie GitHub Actions, mit einigen Unterschieden. Erstellen Sie .gitea/workflows/build.yml in Ihrem Repository:

name: Build and Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.23'

      - name: Build
        run: go build -v ./...

      - name: Test
        run: go test -v ./...

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Deploy via SSH
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
        run: |
          mkdir -p ~/.ssh
          echo "$DEPLOY_KEY" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh -o StrictHostKeyChecking=accept-new user@production "cd /app && git pull && systemctl restart myapp"

Speichern Sie das DEPLOY_KEY-Secret in Ihren Repository-Einstellungen unter Settings > Actions > Secrets.

Funktion Gitea Actions GitHub Actions
Workflow-Verzeichnis .gitea/workflows/ .github/workflows/
uses: actions/* Funktioniert (wird standardmäßig von GitHub abgerufen) Nativ
Container-Services Unterstützt Unterstützt
Matrix-Builds Unterstützt Unterstützt
Wiederverwendbare Workflows Unterstützt seit Gitea 1.24 Unterstützt
Marketplace-Actions Die meisten funktionieren, manche benötigen Anpassung Nativ

Wie aktivieren Sie Git LFS in Gitea?

Git Large File Storage ermöglicht das Tracking von Binärdateien (Bilder, Modelle, Datensätze), ohne Ihr Repository aufzublähen. Gitea hat eine integrierte LFS-Unterstützung. Wir haben sie bereits mit LFS_START_SERVER=true in der Compose-Datei aktiviert. Die LFS-Daten werden standardmäßig im gitea-data-Volume gespeichert.

Um den Speicherpfad explizit zu konfigurieren oder Limits zu erhöhen, bearbeiten Sie die Gitea-Konfiguration:

docker exec -i gitea sh -c 'cat >> /data/gitea/conf/app.ini' << 'EOF'

[lfs]
PATH = /data/git/lfs
EOF

Starten Sie Gitea neu, um die Änderungen zu übernehmen:

docker compose restart gitea

Auf der Clientseite installieren Sie git-lfs und tracken Dateitypen:

git lfs install
git lfs track "*.bin" "*.h5" "*.onnx"
git add .gitattributes
git commit -m "Track model files with LFS"
git push

LFS-Dateien werden auf dem Server unter dem konfigurierten PATH im Container gespeichert. Für große Deployments können Sie S3-kompatiblen Speicher in app.ini unter der [lfs]-Sektion konfigurieren.

Wie spiegeln Sie GitHub-Repositories zu Gitea?

Pull-Mirroring erstellt eine schreibgeschützte Kopie eines GitHub-Repositorys auf Ihrer Gitea-Instanz. Die Synchronisation erfolgt automatisch nach einem konfigurierbaren Zeitplan. Nützlich für Backups, als lokaler Cache hinter Ihrem CI-Runner oder um die Abhängigkeit von GitHub zu reduzieren.

In der Gitea-Web-Oberfläche:

  1. Klicken Sie auf + > New Migration
  2. Wählen Sie GitHub als Quelle
  3. Geben Sie die Repository-URL ein (z. B. https://github.com/owner/repo.git)
  4. Für private Repos geben Sie Ihren GitHub-Benutzernamen und ein Personal Access Token als Passwort ein
  5. Aktivieren Sie This repository will be a mirror
  6. Legen Sie das Spiegelungsintervall fest (Standard: 8 Stunden)
  7. Klicken Sie auf Migrate Repository

Der Mirror synchronisiert Branches, Tags und Releases. Issues, Pull Requests und Wikis können beim initialen Import migriert werden, werden aber nicht fortlaufend synchronisiert.

Zum Spiegeln über die API:

curl -X POST "https://git.example.com/api/v1/repos/migrate" \
  -H "Authorization: token YOUR_GITEA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "clone_addr": "https://github.com/owner/repo.git",
    "mirror": true,
    "mirror_interval": "8h",
    "repo_name": "repo-mirror",
    "repo_owner": "admin",
    "service": "github"
  }'

Wie konfigurieren Sie Webhooks für das Deployment?

Webhooks senden HTTP-POST-Anfragen an eine URL, wenn Ereignisse in einem Repository auftreten. Sie sind eine einfache Möglichkeit, Deployments, Benachrichtigungen oder externe CI-Systeme auszulösen.

In Ihrem Repository gehen Sie zu Settings > Webhooks > Add Webhook > Gitea:

  • Target URL: https://deploy.example.com/hooks/gitea
  • HTTP Method: POST
  • Content Type: application/json
  • Secret: generieren Sie mit openssl rand -hex 32
  • Trigger events: Push events (oder anpassen)

Auf der Empfängerseite muss Ihr Deployment-Skript die Webhook-Signatur überprüfen:

# The webhook sends a X-Gitea-Signature header
# Verify it with the shared secret before acting on the payload
EXPECTED=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')
if [ "$SIGNATURE" != "$EXPECTED" ]; then
  echo "Invalid signature"
  exit 1
fi

Ein minimaler Webhook-Empfänger mit einem leichtgewichtigen Tool wie webhook:

[
  {
    "id": "deploy",
    "execute-command": "/opt/deploy.sh",
    "command-working-directory": "/opt/app",
    "trigger-rule": {
      "match": {
        "type": "payload-hmac-sha256",
        "secret": "your-webhook-secret",
        "parameter": {
          "source": "header",
          "name": "X-Gitea-Signature"
        }
      }
    }
  }
]

Wie sichern Sie eine Gitea-Instanz?

Gitea liefert einen gitea dump-Befehl, der Repositories, die Datenbank, Konfiguration und LFS-Objekte in eine einzelne Zip-Datei packt. Für PostgreSQL benötigen Sie zusätzlich einen separaten pg_dump für Point-in-Time-Recovery.

Manuelles Backup

docker exec -it gitea /usr/local/bin/gitea dump -c /data/gitea/conf/app.ini --file /data/gitea-backup.zip
docker cp gitea:/data/gitea-backup.zip /opt/gitea/backups/

Das Dump enthält:

Inhalt Im Dump enthalten
Git-Repositories Ja
Datenbank (SQLite oder Dump) Ja
Konfiguration (app.ini) Ja
LFS-Objekte Ja
Anhänge, Avatare Ja
Pakete Nein (separat sichern)

Für PostgreSQL zusätzlich:

docker exec gitea-db pg_dump -U gitea gitea | gzip > /opt/gitea/backups/gitea-db-$(date +%Y%m%d).sql.gz

Automatisiertes Backup mit Cron

sudo mkdir -p /opt/gitea/backups
sudo chown root:root /opt/gitea/backups
sudo chmod 700 /opt/gitea/backups

Erstellen Sie das Backup-Skript:

sudo tee /opt/gitea/backup.sh > /dev/null << 'BACKUP'
#!/bin/bash
set -euo pipefail

BACKUP_DIR="/opt/gitea/backups"
DATE=$(date +%Y%m%d-%H%M)

# Gitea dump
docker exec gitea /usr/local/bin/gitea dump -c /data/gitea/conf/app.ini --file /data/gitea-backup.zip --quiet
docker cp gitea:/data/gitea-backup.zip "$BACKUP_DIR/gitea-dump-$DATE.zip"
docker exec gitea rm /data/gitea-backup.zip

# PostgreSQL dump
docker exec gitea-db pg_dump -U gitea gitea | gzip > "$BACKUP_DIR/gitea-db-$DATE.sql.gz"

# Keep last 7 daily backups
find "$BACKUP_DIR" -name "gitea-*" -mtime +7 -delete

echo "Backup completed: $DATE"
BACKUP
sudo chmod 700 /opt/gitea/backup.sh
ls -la /opt/gitea/backup.sh
-rwx------ 1 root root 523 Mar 20 10:00 /opt/gitea/backup.sh

Planen Sie es täglich um 3 Uhr morgens:

echo "0 3 * * * root /opt/gitea/backup.sh >> /var/log/gitea-backup.log 2>&1" | sudo tee /etc/cron.d/gitea-backup
sudo chmod 644 /etc/cron.d/gitea-backup

Testen Sie das Skript einmal manuell:

sudo /opt/gitea/backup.sh
Backup completed: 20260320-1030
ls -lh /opt/gitea/backups/
-rw-r--r-- 1 root root 2.3M Mar 20 10:30 gitea-dump-20260320-1030.zip
-rw-r--r-- 1 root root  48K Mar 20 10:30 gitea-db-20260320-1030.sql.gz

Kopieren Sie Backups vom Server weg. Ein lokales Backup, das mit der Festplatte stirbt, ist kein Backup.

Wie aktualisieren Sie Gitea sicher?

Ziehen Sie das neue Image, erstellen Sie den Container neu und lassen Sie Gitea die Datenbankmigrationen automatisch durchführen. Pinnen Sie Image-Versionen, damit Updates beabsichtigt sind, nicht versehentlich.

cd /opt/gitea

# Back up first
sudo /opt/gitea/backup.sh

# Update the image tag in docker-compose.yml, then:
docker compose pull gitea
docker compose up -d gitea
docker compose logs gitea --tail 30

Achten Sie auf Migrationsmeldungen:

2026/03/20 10:35:00 ...les/migration.go:67:Migrate() [I] Migration completed

Nachdem Sie bestätigt haben, dass Gitea sauber startet, aktualisieren Sie den Runner, falls eine neue Version verfügbar ist:

docker compose pull runner
docker compose up -d runner

Was ist der Unterschied zwischen Gitea und Forgejo?

Forgejo hat sich Ende 2022 von Gitea abgespalten, nachdem ein gewinnorientiertes Unternehmen (Gitea Ltd.) die Kontrolle über das Gitea-Projekt übernommen hatte. Forgejo wird von Codeberg e.V. geleitet, einem deutschen gemeinnützigen Verein. Anfang 2026 ist Forgejo ein Hard Fork mit auseinanderlaufenden Codebases. Beide funktionieren mit einem ähnlichen Docker-Setup, aber die Migration zwischen den beiden ist nicht mehr nahtlos.

Gitea Forgejo
Governance Gitea Ltd. (gewinnorientiert) Codeberg e.V. (gemeinnützig)
Lizenz MIT GPL-3.0+ (seit Forgejo v9.0)
Docker-Image docker.gitea.com/gitea codeberg.org/forgejo/forgejo
Actions-Unterstützung Ja (act_runner) Ja (kompatibler Runner)
API-Kompatibilität GitHub-kompatibel GitHub-kompatibel
Einzigartige Funktionen Gitea Enterprise, MCP-Server Föderation (ForgeFed), Moderationstools

Wenn Sie ein gemeinschaftlich verwaltetes Projekt mit einer Copyleft-Lizenz möchten, wählen Sie Forgejo. Wenn Sie das Originalprojekt mit kommerzieller Unterstützung möchten, wählen Sie Gitea. Das Docker-Compose-Setup aus dieser Anleitung funktioniert für beide mit minimalen Änderungen (Image tauschen, Pfade anpassen).

Wenn Sie mit Gitea starten und später wechseln möchten: Exportieren Sie Ihre Daten mit gitea dump, richten Sie Forgejo ein und importieren Sie. Testen Sie gründlich. Die Codebases haben sich so weit auseinander entwickelt, dass einige Datenbankschemata unterschiedlich sind.

Wie viel RAM und CPU braucht Gitea?

Eine Gitea-Instanz mit PostgreSQL verbraucht im Leerlauf etwa 150-250 MB RAM. Bei aktiver Nutzung mit 5-10 Benutzern und CI-Runnern rechnen Sie mit 300-500 MB insgesamt. Das ist etwa 10-mal leichter als eine GitLab-Instanz, die mindestens 4 GB benötigt.

Diese Zahlen stammen von einer laufenden Gitea-Instanz auf einem Virtua Cloud VPS (4 vCPU, 8 GB RAM):

docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
NAME            CPU %     MEM USAGE / LIMIT
gitea           0.15%     148.2MiB / 7.77GiB
gitea-db        0.08%     45.3MiB / 7.77GiB
gitea-runner    0.02%     32.1MiB / 7.77GiB
Szenario RAM (gesamter Stack) CPU
Leerlauf, wenige Repos ~230 MB < 1 %
Aktiv, 5-10 Benutzer ~400 MB 2-5 %
CI-Build läuft ~600 MB (Spitzen während der Builds) 20-50 % pro Job
GitLab-Äquivalent 4.000+ MB 10 %+ im Leerlauf

Gitea läuft problemlos auf einem 2-GB-VPS. Mit CI-Runnern geben Ihnen 4 GB Spielraum für gleichzeitige Builds.

Fehlerbehebung

SSH-Verbindung abgelehnt: Prüfen Sie, ob der git-Benutzer existiert, sshd_config den Match-Block enthält und sshd neu gestartet wurde. Prüfen Sie die Logs:

journalctl -u sshd -f

Runner nimmt keine Jobs an: Bestätigen Sie, dass der Runner im Admin-Panel registriert ist (Site Administration > Actions > Runners). Prüfen Sie die Runner-Logs:

docker compose logs runner --tail 50

Datenbankverbindungsfehler beim Start: Der Health Check sollte das verhindern, aber falls Gitea vor PostgreSQL startet:

docker compose restart gitea

LFS-Push schlägt mit 413 fehl: Erhöhen Sie client_max_body_size in Ihrer Nginx-Konfiguration. 512M reicht normalerweise, aber passen Sie es an Ihre größten Dateien an.

Gitea-Logs:

docker compose logs gitea --tail 100

Oder live verfolgen:

docker compose logs gitea -f

Copyright 2026 Virtua.Cloud. Alle Rechte vorbehalten. Dieser Inhalt ist ein Originalwerk des Virtua.Cloud-Teams. Vervielfältigung, Wiederveröffentlichung oder Weiterverbreitung ohne schriftliche Genehmigung ist untersagt.

Gitea auf einem VPS selbst hosten mit Docker Compose