<?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>Ci-Cd – Serverküche</title><link>https://serverkueche.de/tags/ci-cd/</link><description>Ci-Cd – 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/ci-cd/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></channel></rss>