<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Git – Serverküche</title><link>https://serverkueche.de/tags/git/</link><description>Git – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>de-DE</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Mon, 03 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/tags/git/index.xml" rel="self" type="application/rss+xml"/><item><title>Forgejo: der eigene Git-Server hinter Traefik</title><link>https://serverkueche.de/tutorials/forgejo-git-server/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/tutorials/forgejo-git-server/</guid><description>Forgejo mit Docker &amp; Traefik aufsetzen: eigener Git-Server mit HTTPS, Repos per Web-UI, Klonen über HTTPS und SSH – die selbstgehostete GitHub-Alternative.</description><content:encoded><![CDATA[<p>GitHub ist bequem – aber dein Code liegt dann auf fremden Servern. Mit <strong>Forgejo</strong> hostest du deine
Repositories selbst: eine schlanke, komplett quelloffene Git-Plattform, die auf dem kleinsten VPS
läuft und trotzdem Issues, Pull-Requests, Wiki und CI mitbringt.</p>
<h2 id="was-bauen-wir">Was bauen wir?</h2>
<p>Am Ende läuft <strong>Forgejo 16.0.3</strong> als einzelner Container hinter deinem Traefik, erreichbar unter
<code>https://DEINE_DOMAIN</code> mit automatischem Let&rsquo;s-Encrypt-Zertifikat. Du legst Repositories über die
Web-Oberfläche an und klonst/pushst sie wahlweise <strong>über HTTPS</strong> oder <strong>über SSH</strong>. Als Datenbank
nutzen wir <strong>SQLite</strong> – für einen persönlichen oder kleinen Team-Git-Server reicht das locker und
spart einen zusätzlichen Datenbank-Container. Forgejo ist der Community-Fork von Gitea und in der
Selfhosting-Szene klar im Aufwind.</p>
<p>Warum überhaupt selbst hosten? Dein Code, deine Issues und dein Projektverlauf liegen dann
ausschließlich auf <strong>deinem</strong> Server – keine Abhängigkeit von den Konditionen, Rate-Limits oder
Übernahmen eines externen Anbieters, und volle Datenhoheit. Forgejo ist dabei kein abgespecktes
Spielzeug: Es kann fast alles, was du von GitHub kennst (siehe Schritt 7), bleibt aber schlank genug
für einen kleinen VPS.</p>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li>Ein Server mit <strong>Debian 13</strong> und laufendem Docker (getestet auf einem netcup VPS).</li>
<li>Ein <strong><a href="/tutorials/reverse-proxy-traefik/">Reverse Proxy mit Traefik</a></strong> (das <code>proxy</code>-Netz und der
Resolver <code>le</code> daraus werden vorausgesetzt) – Forgejo bringt kein eigenes HTTPS mit, Traefik
übernimmt die Verschlüsselung.</li>
<li>Eine <strong>(Sub-)Domain</strong>, die per A-/AAAA-Record auf deinen Server zeigt (<code>DEINE_DOMAIN</code>).</li>
<li>Für echte Backups: <strong><a href="/tutorials/backups-mit-restic/">verschlüsselte Backups mit Restic</a></strong>.</li>
</ul>
<p>Forgejo ist genügsam und läuft schon auf dem kleinsten vServer. Wie viel Server dein geplantes Setup
insgesamt braucht, schätzt dir der <a href="/serverempfehlung/">Server-Rechner</a>.</p>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20"
     data-track-content data-content-name="Affiliate-Box · /tutorials/forgejo-git-server/" data-content-piece="VPS 1000 G12">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Empfehlung</span>
    <span title="Mit * markierte Links sind Affiliate-Links.">Anzeige</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 1000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCore · 8 GB RAM · 256 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">ab 10,36 €/Monat</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Forgejo mit SQLite läuft entspannt auf dem VPS 1000.</p>
    </div>
    <a href="https://www.netcup.com/de/server/vps/vps-1000-g12-12m?ref=44083" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 1000 G12 · {page}"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Zu netcup →
</a>

  </div><div class="px-4 pb-4"><p class="not-prose my-3 flex flex-wrap items-center gap-x-2 gap-y-1 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <span>💶 <strong>5 € Gutschein</strong> für netcup-Neukunden:</span><code data-track-voucher="36nc17844976032"
        class="rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</code>
  <span class="text-xs text-slate-500 dark:text-slate-400">(nur Neukunden, keine Domains)</span>
</p></div>
</div>

<h2 id="schritt-für-schritt">Schritt für Schritt</h2>
<h3 id="schritt-1-compose-datei-anlegen">Schritt 1: Compose-Datei anlegen</h3>
<p>Lege einen Ordner für den Stack an und wechsle hinein:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /opt/forgejo <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /opt/forgejo</span></span></code></pre></div>
</div>
<p>Erstelle die Datei <code>compose.yaml</code>. Ersetze <code>DEINE_DOMAIN</code> durch deine echte Domain:</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">forgejo</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">codeberg.org/forgejo/forgejo:16.0.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">forgejo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">USER_UID</span><span class="p">:</span><span class="w"> </span><span class="m">1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">USER_GID</span><span class="p">:</span><span class="w"> </span><span class="m">1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">FORGEJO__server__DOMAIN</span><span class="p">:</span><span class="w"> </span><span class="l">DEINE_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">FORGEJO__server__ROOT_URL</span><span class="p">:</span><span class="w"> </span><span class="l">https://DEINE_DOMAIN/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">FORGEJO__server__SSH_DOMAIN</span><span class="p">:</span><span class="w"> </span><span class="l">DEINE_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">FORGEJO__server__START_SSH_SERVER</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">FORGEJO__server__SSH_PORT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;2222&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">FORGEJO__server__SSH_LISTEN_PORT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;2222&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">FORGEJO__service__DISABLE_REGISTRATION</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">FORGEJO__database__DB_TYPE</span><span class="p">:</span><span class="w"> </span><span class="l">sqlite3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">forgejo_data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/etc/timezone:/etc/timezone:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/etc/localtime:/etc/localtime:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;2222:2222&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">healthcheck</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;CMD&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;wget&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-q&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-O&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/dev/null&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;http://localhost:3000/api/healthz&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">start_period</span><span class="p">:</span><span class="w"> </span><span class="l">30s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">start_interval</span><span class="p">:</span><span class="w"> </span><span class="l">2s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">30s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">5s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.enable=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.forgejo.rule=Host(`DEINE_DOMAIN`)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.forgejo.entrypoints=websecure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.forgejo.tls.certresolver=le&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.services.forgejo.loadbalancer.server.port=3000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">proxy]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">forgejo_data</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p>Die wichtigsten Stellen im Detail:</p>
<ul>
<li><strong><code>FORGEJO__…</code>-Variablen</strong> konfigurieren Forgejo direkt über Umgebungsvariablen (Abschnitt und
Schlüssel per doppeltem Unterstrich). <code>ROOT_URL</code> muss exakt die öffentliche HTTPS-Adresse sein,
sonst zeigen Klon-Links und Weiterleitungen ins Leere.</li>
<li><strong>Git über SSH</strong> übernimmt Forgejos <strong>eingebauter SSH-Server</strong> (<code>START_SSH_SERVER=true</code>) – so
musst du den gehärteten SSH-Zugang deines Hosts (Port 22) nicht anfassen. Er lauscht im Container
auf <strong>2222</strong> und wird auf denselben Host-Port veröffentlicht.</li>
<li><strong><code>loadbalancer.server.port=3000</code></strong> sagt Traefik, dass die Weboberfläche intern auf Port 3000
läuft. Nur SSH (2222) wird direkt als Port veröffentlicht – der Web-Zugriff geht ausschließlich
über Traefik.</li>
<li>Der <strong>Healthcheck</strong> mit <strong><code>start_interval: 2s</code></strong> ist bewusst so gesetzt (mehr dazu in
„Wenn es nicht funktioniert&quot;).</li>
<li><strong><code>DISABLE_REGISTRATION: &quot;true&quot;</code></strong> schließt die offene Selbst-Registrierung von Anfang an – dein
Git-Server steht damit nicht für Fremde offen. Dein <strong>Admin-Konto</strong> legst du trotzdem ganz normal im
Erstinstallations-Wizard an (Schritt 3); alternativ könntest du es über <code>FORGEJO__admin__*</code>-Variablen
automatisiert erzeugen.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>SSH-Port: unbedingt beide Werte setzen
  </p>
  <div class="prose-kitchen text-sm"><code>SSH_PORT</code> (die im Klon-Link angezeigte Portnummer) <strong>und</strong> <code>SSH_LISTEN_PORT</code> (der Port, auf dem der
Server tatsächlich lauscht) müssen zusammenpassen. Setzt du nur <code>SSH_PORT</code> und lässt <code>SSH_LISTEN_PORT</code>
weg, versucht Forgejo auf dem alten Standard-Port zu lauschen und stürzt beim Start mit
<code>bind: address already in use</code> in eine Neustart-Schleife.</div>
</div>
<h3 id="schritt-2-starten-und-auf-healthy-warten">Schritt 2: Starten und auf „healthy&quot; warten</h3>
<p>Starte den Container:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d</span></span></code></pre></div>
</div>
<p>Prüfe den Status:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose ps</span></span></code></pre></div>
</div>
<p>Nach wenigen Sekunden steht der Container auf <code>healthy</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NAME      IMAGE                                 SERVICE   STATUS                    PORTS
</span></span><span class="line"><span class="cl">forgejo   codeberg.org/forgejo/forgejo:16.0.3   forgejo   Up 12 seconds (healthy)   22/tcp, 3000/tcp, 0.0.0.0:2222-&gt;2222/tcp, [::]:2222-&gt;2222/tcp</span></span></code></pre></div>
</div>
<p>Das <code>22/tcp</code> in der Portliste ist nur ein vom Image deklarierter Port ohne Veröffentlichung – nach
außen erreichbar ist ausschließlich der 2222er, den du selbst gemappt hast.</p>
<p>Traefik holt jetzt im Hintergrund das Zertifikat. Prüfe von deinem Rechner aus, dass die Weboberfläche
über HTTPS antwortet:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s https://DEINE_DOMAIN/api/healthz</span></span></code></pre></div>
</div>
<p>Erwartete Ausgabe – Forgejo meldet sich gesund. Solange die Erstinstallation (Schritt 3) noch nicht
durch ist, antwortet der Endpunkt knapp mit Status und Slogan:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">  &#34;status&#34;: &#34;pass&#34;,
</span></span><span class="line"><span class="cl">  &#34;description&#34;: &#34;Forgejo: Beyond coding. We forge.&#34;
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div>
</div>
<p>Nach dem Abschluss des Wizards listet dieselbe URL zusätzlich die Einzelprüfungen <code>cache:ping</code> und
<code>database:ping</code> auf – dann steht dort auch dein Instanzname statt des Slogans.</p>
<h3 id="schritt-3-erstinstallation--admin-konto">Schritt 3: Erstinstallation &amp; Admin-Konto</h3>
<p>Rufe <code>https://DEINE_DOMAIN</code> im Browser auf. Beim ersten Start zeigt Forgejo die <strong>Erstinstallation</strong>.
Die Datenbank- und Server-Einstellungen sind dank der Umgebungsvariablen bereits korrekt vorbelegt –
du musst hier nur noch das <strong>Administrator-Konto</strong> anlegen (klapp den entsprechenden Abschnitt auf und
trage Benutzername, E-Mail und ein starkes Passwort ein). Ein Klick auf <strong>Forgejo installieren</strong>, und
nach einem kurzen Neustart landest du auf deinem leeren Dashboard:</p>
<p><figure class="my-6"><img src="/tutorials/forgejo-git-server/forgejo-dashboard_hu_447d2ecefd545da2.webp" srcset="/tutorials/forgejo-git-server/forgejo-dashboard_hu_41c9c5a7237c94f4.webp 480w, /tutorials/forgejo-git-server/forgejo-dashboard_hu_447d2ecefd545da2.webp 768w, /tutorials/forgejo-git-server/forgejo-dashboard_hu_f57c270d3cd8cd3b.webp 1200w, /tutorials/forgejo-git-server/forgejo-dashboard_hu_ad5377052900790.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/forgejo-git-server/forgejo-dashboard_hu_23f232f35e4388de.webp"
    alt="Forgejo-Dashboard direkt nach der Ersteinrichtung – noch ohne Repositories." title="Das Dashboard nach dem ersten Login."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Das Dashboard nach dem ersten Login.</figcaption></figure></p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Tipp
  </p>
  <div class="prose-kitchen text-sm">Die Install-Seite bleibt offen, bis der Wizard einmal abgeschlossen ist – schließe ihn deshalb
<strong>sofort nach <code>docker compose up -d</code></strong> ab und lege dabei dein Admin-Konto an. Die offene
Selbst-Registrierung ist bereits über <code>FORGEJO__service__DISABLE_REGISTRATION: &quot;true&quot;</code> in der Compose
deaktiviert, sodass sich niemand von außen ein Konto anlegen kann.</div>
</div>
<h3 id="schritt-4-erstes-repository-anlegen">Schritt 4: Erstes Repository anlegen</h3>
<p>Klicke oben rechts auf das <strong>+</strong> und dann auf <strong>Neues Repository</strong>. Vergib einen Namen und setze den
Haken bei <strong>Repository initialisieren</strong> (legt direkt eine <code>README</code> an, damit das Repo nicht leer ist):</p>
<p><figure class="my-6"><img src="/tutorials/forgejo-git-server/forgejo-repo-anlegen_hu_41cf15acde3fe48.webp" srcset="/tutorials/forgejo-git-server/forgejo-repo-anlegen_hu_544d920b7a0a2cb8.webp 480w, /tutorials/forgejo-git-server/forgejo-repo-anlegen_hu_41cf15acde3fe48.webp 768w, /tutorials/forgejo-git-server/forgejo-repo-anlegen_hu_c947b8293fb70f50.webp 1200w, /tutorials/forgejo-git-server/forgejo-repo-anlegen_hu_1c77b79b5083bd73.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/forgejo-git-server/forgejo-repo-anlegen_hu_e53e166be10d94e7.webp"
    alt="Formular „Neues Repository&quot; in Forgejo mit Namensfeld und Initialisierungs-Option." title="Ein neues Repository anlegen."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Ein neues Repository anlegen.</figcaption></figure></p>
<p>Nach dem Anlegen siehst du die Repository-Ansicht. Über den Umschalter <strong>HTTPS / SSH</strong> bekommst du die
passende Klon-Adresse:</p>
<p><figure class="my-6"><img src="/tutorials/forgejo-git-server/forgejo-repository_hu_a23720c92b53dc87.webp" srcset="/tutorials/forgejo-git-server/forgejo-repository_hu_d1ede266fbf7c05a.webp 480w, /tutorials/forgejo-git-server/forgejo-repository_hu_a23720c92b53dc87.webp 768w, /tutorials/forgejo-git-server/forgejo-repository_hu_ca5bc431b01f2c81.webp 1200w, /tutorials/forgejo-git-server/forgejo-repository_hu_8381af427d117c72.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/forgejo-git-server/forgejo-repository_hu_ba7b8f4eb9ff691b.webp"
    alt="Repository-Ansicht in Forgejo mit README und dem HTTPS/SSH-Umschalter für die Klon-URL." title="Die Klon-URL gibt es wahlweise für HTTPS oder SSH."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Die Klon-URL gibt es wahlweise für HTTPS oder SSH.</figcaption></figure></p>
<h3 id="schritt-5-klonen-und-pushen-über-https">Schritt 5: Klonen und pushen über HTTPS</h3>
<p>Am schnellsten geht es über HTTPS – es funktioniert sofort, ohne Schlüssel einzurichten:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://DEINE_DOMAIN/DEIN_BENUTZER/mein-erstes-repo.git</span></span></code></pre></div>
</div>
<p>Ein <strong>öffentliches</strong> Repository klonst du ohne Anmeldung – erwartete Ausgabe:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cloning into &#39;mein-erstes-repo&#39;...
</span></span><span class="line"><span class="cl">remote: Enumerating objects: 3, done.
</span></span><span class="line"><span class="cl">remote: Counting objects: 100% (3/3), done.
</span></span><span class="line"><span class="cl">remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
</span></span><span class="line"><span class="cl">Receiving objects: 100% (3/3), done.</span></span></code></pre></div>
</div>
<p>Bei einem privaten Repository fragt Git schon hier nach Benutzername und Passwort. Damit ist der Kreis
geschlossen – du legst eine Datei an, committest sie und schiebst sie zurück auf deinen Server:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> mein-erstes-repo
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Meine erste Notiz&#34;</span> &gt;&gt; notizen.txt
</span></span><span class="line"><span class="cl">git add notizen.txt
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;Erste Notiz&#34;</span>
</span></span><span class="line"><span class="cl">git push</span></span></code></pre></div>
</div>
<p>Spätestens beim <code>git push</code> will Forgejo wissen, wer du bist: Git fragt nach Benutzername und Passwort.
Am Ende bestätigt Git den neuen Stand auf dem Server:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">To https://DEINE_DOMAIN/DEIN_BENUTZER/mein-erstes-repo.git
</span></span><span class="line"><span class="cl">   f398f79..7bad57b  main -&gt; main</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Tipp
  </p>
  <div class="prose-kitchen text-sm">Auf einem frischen Client scheitert der erste Commit mit <code>Please tell me who you are</code>, solange Git
deinen Namen und deine E-Mail nicht kennt. Setze sie einmalig global:
<code>git config --global user.name &quot;DEIN NAME&quot;</code> und <code>git config --global user.email &quot;DEINE_MAIL&quot;</code>.</div>
</div>
<p>Sekunden später taucht der Commit in der Weboberfläche auf – dein Code liegt jetzt versioniert auf
deinem eigenen Server.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Tipp
  </p>
  <div class="prose-kitchen text-sm">Nutze für HTTPS statt deines Passworts einen <strong>Zugangstoken</strong> (unter <strong>Einstellungen → Anwendungen</strong>).
Der lässt sich einzeln widerrufen und ist Pflicht, sobald du für dein Konto Zwei-Faktor-Auth aktivierst.</div>
</div>
<h3 id="schritt-6-klonen-und-pushen-über-ssh">Schritt 6: Klonen und pushen über SSH</h3>
<p>Für den täglichen Gebrauch ist SSH bequemer (kein Passwort pro Push). Hinterlege dazu deinen
<strong>öffentlichen</strong> SSH-Schlüssel unter <strong>Einstellungen → SSH- / GPG-Schlüssel → Schlüssel hinzufügen</strong>:</p>
<p><figure class="my-6"><img src="/tutorials/forgejo-git-server/forgejo-ssh-key_hu_d113693f9e3e5938.webp" srcset="/tutorials/forgejo-git-server/forgejo-ssh-key_hu_594fb7b33a925870.webp 480w, /tutorials/forgejo-git-server/forgejo-ssh-key_hu_d113693f9e3e5938.webp 768w, /tutorials/forgejo-git-server/forgejo-ssh-key_hu_f34de0352d2416d6.webp 1200w, /tutorials/forgejo-git-server/forgejo-ssh-key_hu_d6712092153beea8.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/forgejo-git-server/forgejo-ssh-key_hu_9271fba06cd84ab2.webp"
    alt="Forgejo-Einstellungsseite „SSH-Schlüssel verwalten&quot; mit Button „Schlüssel hinzufügen&quot;." title="Den öffentlichen SSH-Schlüssel im Konto hinterlegen."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Den öffentlichen SSH-Schlüssel im Konto hinterlegen.</figcaption></figure></p>
<p>Deinen öffentlichen Schlüssel zeigst du dir lokal mit <code>cat ~/.ssh/id_ed25519.pub</code> an (falls noch keiner
existiert: <code>ssh-keygen -t ed25519</code>). Danach klonst du über SSH – beachte den <strong>Port 2222</strong>:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone ssh://git@DEINE_DOMAIN:2222/DEIN_BENUTZER/mein-erstes-repo.git</span></span></code></pre></div>
</div>
<p>Damit das klappt, muss der SSH-Port <strong>in beiden Firewalls offen sein</strong> – in der
<a href="/tutorials/firewall-ufw-einrichten/">UFW-Firewall</a> (<code>ufw allow 2222/tcp</code>) und, falls genutzt, in der
<a href="/tutorials/netcup-firewall-einrichten/">netcup-Firewall</a>.</p>
<h3 id="schritt-7-mehr-als-nur-git-hosting">Schritt 7: Mehr als nur Git-Hosting</h3>
<p>Ein Repository und Klonen sind erst der Anfang – Forgejo bringt die komplette Werkbank drumherum mit.
Diese Funktionen findest du in der Navigationsleiste jedes Repositories bzw. deines Kontos:</p>
<ul>
<li><strong>Issues &amp; Pull-Requests:</strong> vollwertiges Ticket-System und Code-Review inklusive Labels,
Meilensteinen und Zuweisungen – die Basis für Zusammenarbeit im Team.</li>
<li><strong>Forgejo Actions:</strong> eine zu <strong>GitHub Actions kompatible</strong> CI/CD-Engine. Bestehende
<code>.github/workflows</code>-Dateien laufen oft ohne Änderung. Actions brauchen allerdings einen separaten
<strong>Runner</strong>, der die Jobs ausführt – der gehört aus Sicherheitsgründen nicht auf den Produktiv-Host
und hat ein eigenes Tutorial:
<a href="/tutorials/forgejo-actions-runner/">Forgejo Actions: eigener CI/CD-Runner mit Docker</a> (führt
dedizierte Kerne vor: ein guter Anlass für einen Root-Server, siehe
<a href="/netcup-empfehlung/">netcup-Empfehlung</a>).</li>
<li><strong>Wiki &amp; Releases:</strong> Dokumentation direkt am Projekt und versionierte Release-Downloads.</li>
<li><strong>Paket-Registry:</strong> Forgejo kann Container-Images, npm-, Maven-, PyPI- und weitere Pakete hosten –
praktisch, wenn du eigene Artefakte ablegen willst, ohne einen weiteren Dienst zu betreiben.</li>
<li><strong>Organisationen &amp; Teams:</strong> bündele Repositories und steuere Zugriffsrechte pro Team.</li>
</ul>
<p>Du musst deine Projekte auch nicht von Hand umziehen: Über <strong>+ → Migration</strong> importiert Forgejo ein
bestehendes Repository von GitHub, GitLab oder einer anderen Forgejo-/Gitea-Instanz – inklusive
Issues, Pull-Requests und Releases, nicht nur der Git-Historie. So wechselst du in wenigen Minuten von
GitHub auf deinen eigenen Server, ohne etwas zu verlieren.</p>
<h2 id="wenn-es-nicht-funktioniert">Wenn es nicht funktioniert</h2>
<div class="troubleshoot not-prose">
<p><strong>Der Container startet immer wieder neu (<code>Restarting</code>), im Log steht <code>bind: address already in use</code>.</strong>
Der eingebaute SSH-Server kollidiert mit sich selbst, weil <code>SSH_PORT</code> und <code>SSH_LISTEN_PORT</code> nicht
zusammenpassen. Setze <strong>beide</strong> auf denselben Wert (hier <code>2222</code>) – dann startet Forgejo sauber.</p>
<p><strong>Der Container braucht ewig, bis er <code>healthy</code> ist.</strong> Docker führt den ersten Healthcheck standardmäßig
erst nach dem <code>interval</code> (30 s) aus – der Container sieht also 30 s+ „ungesund&quot; aus, obwohl Forgejo
längst in ~2 s bereit ist. Die Lösung steckt schon oben in der Compose: <strong><code>start_interval: 2s</code></strong> prüft
während der Startphase im 2-Sekunden-Takt und schaltet sofort auf <code>healthy</code>, sobald die App antwortet.
(Braucht Docker 25+ / Compose v2.20+ – auf Debian 13 gegeben.)</p>
<p><strong>Traefik liefert 502 Bad Gateway.</strong> Fast immer der falsche Port: Forgejos Weboberfläche lauscht intern
auf <strong>3000</strong>, deshalb muss <code>loadbalancer.server.port=3000</code> gesetzt sein und der Container im
<code>proxy</code>-Netz hängen.</p>
<p><strong>Klon-Links zeigen <code>localhost</code> oder den falschen Port.</strong> Dann stimmen <code>ROOT_URL</code>, <code>SSH_DOMAIN</code> oder
<code>SSH_PORT</code> nicht. Korrigiere die Werte in der Compose und starte mit <code>docker compose up -d</code> neu.</p>
<p><strong>SSH-Klon scheitert mit <code>Permission denied (publickey)</code>.</strong> Der SSH-Server läuft, aber dein
öffentlicher Schlüssel ist noch nicht im Konto hinterlegt (Schritt 6) – oder du hast den Port <code>2222</code>
vergessen.</p>

</div>

<h2 id="wartung--backups">Wartung &amp; Backups</h2>
<p><strong>Alles liegt im Volume <code>forgejo_data</code></strong> (<code>/data</code>): die SQLite-Datenbank, deine Repositories und die
Host-Keys des SSH-Servers. Genau dieses Volume gehört gesichert – am saubersten mit
<a href="/tutorials/backups-mit-restic/">restic</a>. Weil eine SQLite-Datei im Betrieb beschrieben wird, sicherst
du sie konsistent, indem du entweder den Container kurz stoppst (<code>docker compose stop</code>) oder Forgejos
eingebauten Dump nutzt:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> -u git forgejo forgejo dump -t /tmp -f /tmp/forgejo-dump.zip</span></span></code></pre></div>
</div>
<p><code>-u git</code> lässt den Dump als <code>git</code>-Benutzer statt als root laufen (als root verweigert Forgejo den
Dump). Nach <code>/tmp</code> statt <code>/data</code>, weil das <code>git</code>-Konto in <code>/data</code> selbst nicht schreiben darf (das
Wurzelverzeichnis <code>/data</code> gehört root, nur die Unterordner wie <code>/data/gitea</code> und <code>/data/git</code>
gehören dem <code>git</code>-Benutzer) und der Dump sich sonst rekursiv in das gepackte Datenverzeichnis
schreiben würde. Die fertige Zip holst du
anschließend aus dem Container heraus – dorthin, wo restic sie mitsichert:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose cp forgejo:/tmp/forgejo-dump.zip ./forgejo-dump.zip</span></span></code></pre></div>
</div>
<p>Die Zip enthält den Datenbank-Dump (<code>forgejo-db.sql</code>), die <code>app.ini</code> und alle Repository-Daten – ein
vollständiger, für sich stehender Wiederherstellungspunkt.</p>
<p><strong>Updates:</strong> Forgejo veröffentlicht regelmäßig neue Versionen (aktuell die 16er-Reihe). Für ein Update
setzt du den neuen Tag in der <code>compose.yaml</code> (statt <code>16.0.3</code>) und ziehst ihn:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose pull <span class="o">&amp;&amp;</span> docker compose up -d</span></span></code></pre></div>
</div>
<p>Weil die Daten im Volume liegen, bleiben Repos und Konten dabei erhalten. Pinne die Version bewusst auf
einen festen Tag statt <code>latest</code> und wirf <strong>vor</strong> einem Major-Sprung einen Blick in die
<a href="https://forgejo.org/releases/">Release-Notes</a> – dort stehen eventuelle Migrations-Hinweise. Mache
außerdem vor jedem Update ein frisches Backup: ein Git-Server ohne funktionierendes Backup ist ein
Klumpenrisiko für deinen gesamten Code.</p>
<p><strong>Absichern:</strong> Ein öffentlich erreichbarer Git-Server ist ein lohnendes Ziel. Drei Dinge solltest du
gleich erledigen: die <strong>offene Registrierung deaktivieren</strong> (siehe Schritt 3), für dein Admin-Konto
unter <strong>Einstellungen → Sicherheit</strong> die <strong>Zwei-Faktor-Authentifizierung</strong> einschalten und für den
Alltag ein normales Konto statt des Administrators nutzen. Halte Forgejo zeitnah aktuell – die
schnellen Releases enthalten auch Sicherheitsfixes. Und veröffentliche nur die Ports, die du wirklich
brauchst: nach außen genügen 443 (Traefik) und dein SSH-Git-Port 2222.</p>
]]></content:encoded></item></channel></rss>