<?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>Forgejo – Serverküche</title><link>https://serverkueche.de/tags/forgejo/</link><description>Forgejo – 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>Fri, 14 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/tags/forgejo/index.xml" rel="self" type="application/rss+xml"/><item><title>Forgejo Actions: eigener CI/CD-Runner mit Docker</title><link>https://serverkueche.de/tutorials/forgejo-actions-runner/</link><pubDate>Fri, 14 Aug 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/tutorials/forgejo-actions-runner/</guid><description>Einen Forgejo-Actions-Runner mit Docker-in-Docker aufsetzen und registrieren: eigene CI/CD-Pipelines auf dem selbstgehosteten Git-Server – Schritt für Schritt.</description><content:encoded><![CDATA[<p>Dein <a href="/tutorials/forgejo-git-server/">Forgejo-Git-Server</a> läuft – aber Code liegt nur da herum,
solange ihn niemand testet und ausrollt. <strong>Forgejo Actions</strong> bringt CI/CD direkt in deine
Git-Plattform: Bei jedem Push laufen automatisch Tests, Builds oder Deployments. Die Arbeit
erledigt ein <strong>Runner</strong>, den du selbst betreibst. Dieses Tutorial setzt einen solchen Runner mit
Docker-in-Docker auf und lässt eine erste Pipeline grün durchlaufen.</p>
<h2 id="was-bauen-wir">Was bauen wir?</h2>
<p>Am Ende hat dein Forgejo einen <strong>registrierten, aktiven Runner</strong>, der Workflows aus dem Verzeichnis
<code>.forgejo/workflows/</code> ausführt. Wir setzen dabei auf <strong>Docker-in-Docker (DinD)</strong>: Der Runner startet
jeden CI-Job in einem eigenen, wegwerfbaren Container, sauber isoliert vom Host. Konkret läuft am
Ende:</p>
<ul>
<li>der <strong>Forgejo-Runner</strong> (<code>code.forgejo.org/forgejo/runner:13.0.0</code>), der bei Forgejo nach Jobs
fragt,</li>
<li>ein <strong>Docker-in-Docker-Sidecar</strong> (<code>docker:29-dind</code>), in dem die Jobs isoliert laufen,</li>
<li>ein Beispiel-Repository mit einem Workflow, der bei jedem Push <strong><code>actions/checkout</code></strong> ausführt und
eine kleine Aktion startet.</li>
</ul>
<p>Forgejo Actions ist weitgehend <strong>kompatibel zu GitHub Actions</strong> – dieselbe Workflow-Syntax, viele
Marketplace-Actions funktionieren unverändert. Du kannst also bestehendes Wissen direkt weiternutzen,
nur eben auf deinem eigenen Server. Getestet mit <strong>Forgejo 16.0.1</strong> und <strong>Runner v13.0.0</strong> auf
<strong>Debian 13 / Docker 29</strong>.</p>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li>Ein <strong>laufender <a href="/tutorials/forgejo-git-server/">Forgejo-Server</a></strong> hinter einem Reverse Proxy,
erreichbar unter einer <strong>öffentlichen HTTPS-Domain</strong> (<code>DEINE_DOMAIN</code>). Die öffentliche URL ist
wichtig – dazu unten mehr.</li>
<li><strong>Docker</strong> auf demselben Server (der Runner und sein DinD-Sidecar laufen als Container).</li>
<li><strong>Admin-Zugang</strong> zu Forgejo, um den Registrierungstoken zu erzeugen.</li>
</ul>
<p>Actions ist seit <strong>Forgejo 1.21</strong> standardmäßig aktiviert. Falls du es in deiner Forgejo-Compose
explizit gesetzt hast (empfohlen), steht dort:</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="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">FORGEJO__actions__ENABLED</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span></span></span></code></pre></div>
</div>
<p>CI-Jobs sind ressourcenhungriger als der reine Git-Server – Builds brauchen CPU und RAM. Für den
Runner-Betrieb neben Forgejo empfehlen wir daher etwas mehr Reserve; wie viel dein konkretes Setup
braucht, schätzt 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-actions-runner/" data-content-piece="VPS 2000 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 2000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">8 vCore · 16 GB RAM · 512 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">ab 19,24 €/Monat</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Forgejo plus Runner und Build-Jobs profitieren vom größeren Tarif.</p>
    </div>
    <a href="https://www.netcup.com/de/server/vps/vps-2000-g12-12m?ref=44083" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 2000 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-registrierungstoken-holen">Schritt 1: Registrierungstoken holen</h3>
<p>Der Runner muss sich einmalig bei Forgejo anmelden. Dafür brauchst du einen <strong>Registrierungstoken</strong>.
Am einfachsten holst du ihn über die Weboberfläche: Melde dich als Administrator an und geh auf
<strong>Administrator-Einstellungen → Actions → Runner</strong>. Dort siehst du alle Runner und oben rechts den
Knopf <strong>Registrierungstoken anzeigen</strong>.</p>
<p><figure class="my-6"><img src="/tutorials/forgejo-actions-runner/forgejo-runner-online_hu_7653d22fc810d443.webp" srcset="/tutorials/forgejo-actions-runner/forgejo-runner-online_hu_f0a81689b08c7628.webp 480w, /tutorials/forgejo-actions-runner/forgejo-runner-online_hu_7653d22fc810d443.webp 768w, /tutorials/forgejo-actions-runner/forgejo-runner-online_hu_9793e063ce2f40c1.webp 1200w, /tutorials/forgejo-actions-runner/forgejo-runner-online_hu_fa07bd80c85f8c1e.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/forgejo-actions-runner/forgejo-runner-online_hu_b84bf3a4bd83e653.webp"
    alt="Die Runner-Verwaltung in den Administrator-Einstellungen von Forgejo mit dem registrierten Runner." title="Site-Administration → Actions → Runner: hier holst du den Token und siehst später den Runner-Status."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Site-Administration → Actions → Runner: hier holst du den Token und siehst später den Runner-Status.</figcaption></figure></p>
<p>Alternativ per Kommandozeile direkt im Forgejo-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 <span class="nb">exec</span> -u git forgejo forgejo actions generate-runner-token</span></span></code></pre></div>
</div>
<p>Das gibt einen langen Token aus – kopiere ihn, du brauchst ihn gleich einmal.</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">Der hier gezeigte Runner wird <strong>global</strong> (für die ganze Instanz) registriert. Du kannst Runner auch
nur an eine <strong>Organisation</strong> oder ein <strong>einzelnes Repository</strong> binden – dann holst du den Token in
den jeweiligen Einstellungen unter <em>Actions → Runner</em>. Für den Anfang ist ein globaler Runner am
praktischsten.</div>
</div>
<h3 id="schritt-2-die-runner-compose-schreiben">Schritt 2: Die Runner-Compose schreiben</h3>
<p>Leg einen eigenen Ordner 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-runner <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /opt/forgejo-runner</span></span></code></pre></div>
</div>
<p>Erstelle die <code>compose.yaml</code>:</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">docker</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">docker:29-dind</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">fjr-docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">privileged</span><span class="p">:</span><span class="w"> </span><span class="kc">true</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">DOCKER_TLS_CERTDIR</span><span class="p">:</span><span class="w"> </span><span class="l">/certs</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">dind_certs:/certs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">runner_data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">runner</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">code.forgejo.org/forgejo/runner:13.0.0</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">fjr-runner</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">depends_on</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">docker]</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">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2376</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DOCKER_CERT_PATH</span><span class="p">:</span><span class="w"> </span><span class="l">/certs/client</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DOCKER_TLS_VERIFY</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1&#34;</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">dind_certs:/certs:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">runner_data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">working_dir</span><span class="p">:</span><span class="w"> </span><span class="l">/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">forgejo-runner daemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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">dind_certs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">runner_data:</span></span></span></code></pre></div>
</div>
<p>Die wichtigsten Punkte:</p>
<ul>
<li>Der DinD-Dienst heißt bewusst <strong><code>docker</code></strong>. Sein automatisch erzeugtes TLS-Zertifikat ist auf
genau diesen Namen ausgestellt – heißt der Dienst anders, scheitert der Runner mit
„certificate is valid for docker, not …&quot; (siehe „Wenn es nicht funktioniert&quot;).</li>
<li><strong><code>privileged: true</code></strong> braucht DinD, um seine eigene Docker-Engine zu betreiben. Das ist der Preis
der Isolation; halte den Runner-Server entsprechend abgesichert.</li>
<li>Der Runner spricht den DinD über <strong><code>DOCKER_HOST: tcp://docker:2376</code></strong> mit TLS an; die Client-Zertifikate
teilt er sich über das Volume <code>dind_certs</code>.</li>
<li>Die Registrierung landet als <code>.runner</code>-Datei im Volume <strong><code>runner_data</code></strong> und übersteht so
Neustarts und Updates.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>Warum Docker-in-Docker?
  </p>
  <div class="prose-kitchen text-sm">Die Alternative wäre, dem Runner den <strong>Docker-Socket des Hosts</strong> (<code>/var/run/docker.sock</code>)
hineinzureichen. Das ist einfacher, gibt den CI-Jobs aber faktisch <strong>Root auf dem Host</strong> – ein
manipulierter Workflow könnte den ganzen Server übernehmen. DinD kapselt die Jobs in einer eigenen
Docker-Instanz und ist die deutlich sicherere Wahl.</div>
</div>
<h3 id="schritt-3-dind-starten-und-den-runner-registrieren">Schritt 3: DinD starten und den Runner registrieren</h3>
<p>Starte zuerst nur den DinD-Sidecar, damit er seine Zertifikate erzeugt:</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 docker</span></span></code></pre></div>
</div>
<p>Jetzt registrierst du den Runner <strong>einmalig</strong>. Ersetze <code>DEIN_TOKEN</code> durch den Token aus Schritt 1:</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 run --rm runner forgejo-runner register <span class="se">\
</span></span></span><span class="line"><span class="cl">  --no-interactive <span class="se">\
</span></span></span><span class="line"><span class="cl">  --instance https://DEINE_DOMAIN <span class="se">\
</span></span></span><span class="line"><span class="cl">  --token DEIN_TOKEN <span class="se">\
</span></span></span><span class="line"><span class="cl">  --name mein-runner <span class="se">\
</span></span></span><span class="line"><span class="cl">  --labels <span class="s2">&#34;docker:docker://node:24-bookworm&#34;</span></span></span></code></pre></div>
</div>
<p>Bei Erfolg endet die Ausgabe mit <code>Runner registered successfully.</code> (eine Warnung, dass <code>register</code>
„deprecated&quot; sei, kannst du ignorieren – es funktioniert).</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>Unbedingt die öffentliche URL verwenden
  </p>
  <div class="prose-kitchen text-sm">Registriere den Runner mit deiner <strong>öffentlichen</strong> Adresse (<code>https://DEINE_DOMAIN</code>) – <strong>nicht</strong> mit
einer internen wie <code>http://forgejo:3000</code>. Grund: Die CI-Jobs laufen im DinD in eigenen Containern
mit <strong>eigenem Netzwerk</strong> und können interne Docker-Namen nicht auflösen. Beim Auschecken müssen sie
den Git-Server aber erreichen. Mit der öffentlichen URL klappt das von überall – mit einem internen
Namen scheitert jeder Job beim <code>checkout</code>.</div>
</div>
<p>Das Label <code>docker:docker://node:24-bookworm</code> bedeutet: Jobs mit <code>runs-on: docker</code> werden in einem
<code>node:24-bookworm</code>-Container ausgeführt (bringt Node.js und die üblichen Build-Tools mit). Node 24
ist die aktuell aktive LTS-Linie – Node 20 ist seit April 2026 aus dem Support.</p>
<h3 id="schritt-4-den-runner-starten-und-status-prüfen">Schritt 4: Den Runner starten und Status prüfen</h3>
<p>Jetzt startest du den ganzen Stack:</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, dass beide Container laufen:</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>Wirf einen Blick ins Runner-Log – hier siehst du, ob die Anmeldung geklappt hat:</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 logs runner</span></span></code></pre></div>
</div>
<p>Du solltest eine Zeile wie <code>declared successfully</code> und <code>[poller] launched</code> sehen – der Runner fragt
Forgejo jetzt aktiv nach Jobs. In der Weboberfläche unter <strong>Administrator-Einstellungen → Actions →
Runner</strong> taucht <code>mein-runner</code> mit einem <strong>grünen Statuspunkt</strong> und dem Label <code>docker</code> auf (siehe
Screenshot oben). Steht er auf <code>Inaktiv</code> mit grünem Punkt, ist alles gut: Er ist verbunden und
wartet nur auf Arbeit.</p>
<h3 id="schritt-5-den-ersten-workflow-anlegen">Schritt 5: Den ersten Workflow anlegen</h3>
<p>Workflows liegen im Repository unter <code>.forgejo/workflows/</code>. Lege in einem beliebigen Repo die Datei
<code>.forgejo/workflows/ci.yml</code> an:</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">name</span><span class="p">:</span><span class="w"> </span><span class="l">CI</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">push]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</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></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v7</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;Commit $GITHUB_SHA wird getestet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">node --version</span></span></span></code></pre></div>
</div>
<p>Zerlegt:</p>
<ul>
<li><strong><code>on: [push]</code></strong> – der Workflow startet bei jedem Push.</li>
<li><strong><code>runs-on: docker</code></strong> – wählt unseren Runner über das Label <code>docker</code> aus.</li>
<li><strong><code>actions/checkout@v7</code></strong> – checkt den Code aus (dieselbe Action wie bei GitHub; Forgejo lädt sie
automatisch aus seinem Action-Register).</li>
<li>Die beiden <code>run</code>-Schritte geben den Commit und die Node-Version aus – ein minimales, aber echtes
Beispiel, das du später durch deine echten Build-/Test-Befehle ersetzt.</li>
</ul>
<p>Committe und pushe die Datei. Der Push löst den Workflow sofort aus.</p>
<h3 id="schritt-6-den-lauf-ansehen">Schritt 6: Den Lauf ansehen</h3>
<p>Öffne im Repository den Reiter <strong>Actions</strong>. Dort erscheint dein Lauf – nach wenigen Sekunden mit
einem <strong>grünen Haken</strong>:</p>
<p><figure class="my-6"><img src="/tutorials/forgejo-actions-runner/forgejo-actions-run_hu_54b41994b9cfef0b.webp" srcset="/tutorials/forgejo-actions-runner/forgejo-actions-run_hu_f32d0e65e4f51fc8.webp 480w, /tutorials/forgejo-actions-runner/forgejo-actions-run_hu_54b41994b9cfef0b.webp 768w, /tutorials/forgejo-actions-runner/forgejo-actions-run_hu_2a6e21f9ecfe4183.webp 1200w, /tutorials/forgejo-actions-runner/forgejo-actions-run_hu_a3e10d18cb2cdd7b.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/forgejo-actions-runner/forgejo-actions-run_hu_d2c7853d757cd8d8.webp"
    alt="Der Actions-Reiter eines Repositories mit einem erfolgreich durchgelaufenen CI-Workflow." title="Der Actions-Reiter: der Workflow-Lauf ist grün."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Der Actions-Reiter: der Workflow-Lauf ist grün.</figcaption></figure></p>
<p>Ein Klick auf den Lauf öffnet die <strong>Job-Ansicht</strong> mit den einzelnen Schritten und ihren Logs. Hier
siehst du, wie <code>actions/checkout</code> das Repository klont und die Befehle nacheinander laufen:</p>
<p><figure class="my-6"><img src="/tutorials/forgejo-actions-runner/forgejo-job-log_hu_9cfc366a70a8935a.webp" srcset="/tutorials/forgejo-actions-runner/forgejo-job-log_hu_2dbc1aaf5b4ec89e.webp 480w, /tutorials/forgejo-actions-runner/forgejo-job-log_hu_9cfc366a70a8935a.webp 768w, /tutorials/forgejo-actions-runner/forgejo-job-log_hu_685ca3f6a11947ed.webp 1200w, /tutorials/forgejo-actions-runner/forgejo-job-log_hu_c17f61e8f3bd5abe.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/forgejo-actions-runner/forgejo-job-log_hu_9b2b9f43bfa5de40.webp"
    alt="Die Detailansicht eines Forgejo-Actions-Jobs mit aufgeklappten Schritt-Logs." title="Job-Detailansicht: alle Schritte grün, mit vollständigen Logs."
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Job-Detailansicht: alle Schritte grün, mit vollständigen Logs.</figcaption></figure></p>
<p>Der <code>node --version</code>-Schritt gibt bei uns <code>v24.20.0</code> aus – der Beweis, dass der Job wirklich im
<code>node:24-bookworm</code>-Container gelaufen ist. Damit steht deine CI/CD: Ab jetzt kannst du in den
<code>run</code>-Schritten testen, bauen und deployen, was du brauchst.</p>
<h2 id="wenn-es-nicht-funktioniert">Wenn es nicht funktioniert</h2>
<div class="troubleshoot not-prose">
<p><strong>Der Runner startet neu und meldet „cannot ping the docker daemon … certificate is valid for
docker, …, not fjr-docker&quot;.</strong> Der DinD-Dienst heißt anders als <code>docker</code>, aber sein TLS-Zertifikat
ist auf <code>docker</code> ausgestellt. Nenne den DinD-Service exakt <strong><code>docker</code></strong> (wie oben) und sprich ihn
über <code>DOCKER_HOST: tcp://docker:2376</code> an – dann passt der Name zum Zertifikat.</p>
<p><strong>Der Job startet, scheitert aber beim <code>actions/checkout</code> mit einem Verbindungsfehler.</strong> Der Runner
wurde mit einer <strong>internen</strong> Instanz-URL (<code>http://forgejo:3000</code>) registriert. Die Job-Container im
DinD können diesen Namen nicht auflösen. Neu registrieren mit der <strong>öffentlichen</strong> URL
<code>https://DEINE_DOMAIN</code> (<code>.runner</code>-Datei im Volume vorher löschen oder das Volume neu anlegen).</p>
<p><strong>Der Runner erscheint gar nicht in der Übersicht / die Registrierung schlägt fehl.</strong> Falscher oder
bereits verbrauchter Token, oder der Runner erreicht Forgejo nicht. Frischen Token holen (Schritt 1)
und prüfen, dass der Runner-Container <code>https://DEINE_DOMAIN</code> erreicht (<code>docker compose run --rm runner wget -qO- https://DEINE_DOMAIN/api/healthz</code>).</p>
<p><strong>Ein Job bleibt ewig „wartend&quot; (pending).</strong> Kein Runner hat ein passendes <strong>Label</strong>. Der Workflow
nutzt <code>runs-on: docker</code>, der Runner muss also das Label <code>docker</code> tragen. Labels beim Registrieren
prüfen; in der Runner-Übersicht werden die Labels je Runner angezeigt.</p>
<p><strong><code>actions/checkout</code> findet die Action nicht.</strong> Forgejo lädt Actions aus einem konfigurierten
Register (standardmäßig <code>data.forgejo.org</code>). Ist der Server komplett vom Internet abgeschnitten,
schlägt das fehl. Ausgehenden HTTPS-Zugriff erlauben oder Actions in einem internen Register
spiegeln.</p>

</div>

<h2 id="wartung--backups">Wartung &amp; Backups</h2>
<p><strong>Updates.</strong> Runner und DinD aktualisierst du wie jeden Compose-Stack:</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> /opt/forgejo-runner
</span></span><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>Halte den Runner <strong>grob auf Augenhöhe mit deiner Forgejo-Version</strong> – eine stark veraltete
Runner-Version kann mit neuen Forgejo-Features Probleme bekommen. Pinne wie oben eine konkrete
Version statt <code>latest</code>, damit Updates bewusst passieren.</p>
<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>Umstieg von Runner v12 auf v13
  </p>
  <div class="prose-kitchen text-sm">Runner <strong>v13</strong> bringt bewusste Brüche mit: Die Workflow-Befehle <code>set-output</code>, <code>set-env</code> und
<code>add-path</code> sind ersatzlos entfernt – schreib stattdessen in die Dateien <code>$FORGEJO_OUTPUT</code>,
<code>$FORGEJO_ENV</code> und <code>$FORGEJO_PATH</code>. Außerdem lassen fehlerhafte Ausdrücke einen Job jetzt hart
scheitern (statt nur zu warnen), und in der Runner-Konfiguration heißt <code>container.network_mode</code>
nur noch <code>container.network</code>. Ein frisch aufgesetzter Runner wie hier ist davon nicht betroffen;
wer bestehende Workflows mitnimmt, liest vorher die
<a href="https://forgejo.org/2026-08-runner-release-v13/">Release-Notes zu v13</a>.</div>
</div>
<p><strong>Backups.</strong> Sicherungswürdig ist vor allem die <strong><code>.runner</code>-Datei</strong> im Volume <code>runner_data</code> – sie
enthält die Registrierung. Geht sie verloren, registriert sich der Runner beim nächsten Start als
<strong>neuer</strong> Runner (der alte bleibt als „offline&quot; in der Übersicht stehen und kann dort gelöscht
werden). Ein Totalverlust ist kein Drama: Du holst einen neuen Token und registrierst neu. Die
DinD-Daten (<code>dind_certs</code>, Job-Caches) sind flüchtig und müssen <strong>nicht</strong> gesichert werden.</p>
<p><strong>Aufräumen.</strong> Die CI-Jobs erzeugen im DinD mit der Zeit ungenutzte Images und Layer. Räum sie
gelegentlich auf, damit die Platte nicht vollläuft:</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> docker docker system prune -af</span></span></code></pre></div>
</div>
<p><strong>Sicherheit.</strong> Der DinD läuft <code>privileged</code> – behandle den Runner-Host wie ein sicherheitskritisches
System: nur nötige Ports offen, keine anderen sensiblen Dienste daneben, und CI nur für Repositories,
deren Workflows du kontrollierst. Wer Workflows aus fremden Forks zulässt, sollte sich vorher intensiv
mit deren Risiken beschäftigen.</p>
]]></content:encoded></item><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>