Tutorial
12 min read
48 views

Zero-Trust CI/CD: Sichere selbstgehostete GitHub Actions Runners via Reverse Tunnels erstellen

IT
InstaTunnel Team
Published by our engineering team
Zero-Trust CI/CD: Sichere selbstgehostete GitHub Actions Runners via Reverse Tunnels erstellen

Moderne Engineering-Teams stehen vor einem bekannten Dilemma: cloudgehostete CI/CD Runners sind teuer und oft unterpowered für anspruchsvolle Workloads, aber einen öffentlichen Plattform wie GitHub Actions in ein firewallgeschütztes Firmennetzwerk zu routen, klingt nach einem IT-Albtraum. Es gibt einen dritten Weg — der outbound-only Reverse Tunnels, ephemere Container und identitätsgeprüfte Authentifizierung nutzt, um Bare-Metal-Performance zu bieten, ohne eine einzige eingehende Portöffnung. Dieser Artikel ist eine produktionsreife Blaupause für diese Architektur.


Das Problem mit “Ein Port öffnen”

Der herkömmliche Ansatz für selbstgehostete Runner setzt eingehende Konnektivität voraus. Ein Runner im lokalen Netzwerk registriert sich bei GitHub und wartet auf Jobs. Damit das funktioniert, muss GitHub in der Lage sein, dein System zu erreichen — was meist bedeutet, eine Lücke in der Firewall zu öffnen.

Dieser Ansatz kollidiert fast sofort mit der Sicherheitsrichtlinie des Unternehmens. Eingehende Ports erfordern Firewall-Regeländerungen, Netzwerkarchitektur-Reviews und laufende Wartung der IP-Allowlists. GitHub operiert aus dynamischen IP-Bereichen in mehreren Rechenzentren weltweit — die Pflege und Aktualisierung dieser CIDR-Blöcke ist eine betriebliche Belastung, die nicht skaliert.

Es gibt ein saubereres Modell: die Verbindung komplett umkehren.

Ein selbstgehosteter Runner kommuniziert mit GitHub via einem HTTP(S)-Long-Poll. Er öffnet eine Verbindung zu GitHub für bis zu 50 Sekunden, um auf Jobzuweisungen zu warten. Wenn nichts kommt, timeoutet er und verbindet sich neu. Der Traffic ist ausschließlich outbound auf Port 443 — dem gleichen Port, den dein Browser für HTTPS nutzt. Firmenfirewalls, die Web-Browsing erlauben, erlauben bereits diesen Traffic. Keine Ausnahmen nötig.


Die Reverse-Tunnel-Architektur

Der entscheidende Punkt ist, dass der Runner keine eingehenden Verbindungen von GitHub benötigt — er holt sich die Arbeit nach außen. Ein Reverse-Tunnel-Agent verstärkt diesen Vorteil, indem er einen persistenten, authentifizierten outbound-Kanal schafft, der auch Webhook- oder Steuerungstraffic transportieren kann.

[ Lokale Bare-Metal-Hardware ]
        |
        | (Outbound TLS/QUIC auf Port 443)
        ▼
[ Identity-Gated Tunnel Proxy ]  ◄─── Mutual TLS / Token-Überprüfung
        ▲
        |  (Webhook / Poll-Traffic)
        |
[ GitHub Actions Steuerungsebene ]

Da die Verbindung innerhalb deines Netzwerks initiiert wird, sieht die Firewall sie nur als eine weitere HTTPS-Anfrage. Der Tunnel-Proxy authentifiziert beide Enden, bevor Traffic fließt, und alle Job-Dispatches erfolgen über diesen verifizierten Kanal.


Identity-Gated Sicherheit: Mehr als nur ein Tunnel

Der Traffic durch einen outbound-Tunnel ist nur so sicher wie die Authentifizierung, die ihn schützt. Produktions-Deployments kombinieren mehrere Schichten:

Mutual TLS (mTLS): Sowohl der Runner-Client als auch der Tunnel-Proxy präsentieren kryptografisch signierte Zertifikate. Keine Seite akzeptiert eine Verbindung von einem nicht authentifizierten Peer.

OIDC / OAuth2 Bindung: Die Tunnel-Session kann an verifizierte Identitätsanbieter gebunden werden — Okta, Google Workspace oder GitHub Organization OIDC-Tokens — sodass nur Runner deiner Organisation Sessions aufbauen können.

Kurzlebige ephemere Tokens: Das Registrierungs-Token, um einen Runner mit GitHub zu verbinden, wird dynamisch über die GitHub API bei jedem Pipeline-Start abgerufen und sofort nach Abschluss des Jobs invalidiert. Es gibt keine dauerhaft gespeicherte Anmeldeinformation auf der Festplatte.

Diese Kontrollen stellen sicher, dass dein lokaler Hardware-Server zwar über den Tunnel erreichbar ist, aber nicht auffindbar, scanbar oder zugänglich ist für alles, was nicht die richtige kryptografische Identität vorweisen kann.


Das echte Bedrohungsmodell (Aktualisiert für 2025–2026)

Bevor du diese Infrastruktur aufbaust, solltest du klar sein, was schiefgehen kann.

Im November 2025 zeigte der Shai-Hulud-Wurm in großem Maßstab, dass selbstgehostete Runner als persistente Hintertüren genutzt werden können, die ausschließlich über die vertrauenswürdigen Kanäle von GitHub kommunizieren. Da der gesamte Traffic zu github.com fließt, sind herkömmliche Netzwerkschutzmaßnahmen hier kaum wirksam. GitHubs eigene Sicherheitsdokumentation ist eindeutig: “Self-hosted Runners für GitHub garantieren nicht, dass sie in ephemeren, sauberen virtuellen Maschinen laufen, und können durch untrusted Code in einem Workflow dauerhaft kompromittiert werden.”

Frühere Supply-Chain-Angriffe, inklusive der Vergiftung der tj-actions/changed-files Action und des Codecov-Hacks, zeigten, dass Angreifer zunehmend CI/CD-Pipelines ins Visier nehmen, weil diese oft privilegierten Zugriff auf Produktionsinfrastruktur, Cloud-Credentials und Paket-Registries haben.

Die hier beschriebene Architektur ist darauf ausgelegt, diese Bedrohungen einzudämmen — nicht sie zu ignorieren.


Schritt-für-Schritt-Blueprint

Komponente 1: Der ephemere Runner-Container

Das erste Prinzip der Sicherheit selbstgehosteter Runner ist, dass der Runner niemals untrusted Code direkt auf dem Host-Betriebssystem ausführt. Eine containerisierte, einmalige Umgebung begrenzt den Schaden, falls etwas schiefgeht.

e Versionshinweis: Ab dem 16. März 2026 verlangt GitHub, dass selbstgehostete Runner mindestens Version v2.329.0 haben (veröffentlicht am 15. Oktober 2025). Ältere Runner werden bei der Registrierung blockiert. Die aktuelle stabile Version ist v2.334.0. Immer die neuesten Releases vom actions/runner releases page ziehen.

FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update 66 apt-get install -y \
    curl \
    sudo \
    git \
    jq \
    build-essential \
    ca-certificates \
    66 rm -rf /var/lib/apt/lists/*

# Nicht-root Runner-Benutzer
RUN useradd -m -s /bin/bash runner 66 \
    usermod -aG sudo runner 66 \
    echo "runner ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers

USER runner
WORKDIR /home/runner

# Auf eine bestimmte Version festlegen e= v2.329.0; siehe Release-Seite für neueste
ARG RUNNER_VERSION="2.334.0"

RUN curl -o github-runner.tar.gz -L \
    https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
    6 tar xzf ./github-runner.tar.gz \
    6 rm github-runner.tar.gz

COPY --chown=runner:runner entrypoint.sh /home/runner/entrypoint.sh
RUN chmod +x /home/runner/entrypoint.sh

ENTRYPOINT ["./entrypoint.sh"]

Der --privileged-Flag darf niemals bei docker run für diesen Container verwendet werden. Ein privilegierter Container hat nahezu die gleichen Zugriffsrechte auf den Host wie ein direkt laufender Root-Prozess.

Komponente 2: Das Einstiegsskript

Das entrypoint.sh orchestriert Registrierung, Ausführung und Bereinigung. Es holt bei jedem Start ein kurzlebiges Registrierungstoken von der GitHub API — es wird kein dauerhaftes Token im Image gespeichert.

#!/bin/bash
set -e

if [ -z "$GH_OWNER" ] || [ -z "$GH_REPOSITORY" ] || [ -z "$GH_PAT" ]; then
    echo "ERROR: Fehlende erforderliche Umgebungsvariablen."
    exit 1
fi

echo "Kurzlebiges Registrierungstoken wird abgerufen..."
REG_TOKEN=$(curl -s -X POST \
    -H "Authorization: token ${GH_PAT}" \
    -H "Accept: application/vnd.github.v3+json" \
    https://api.github.com/repos/${GH_OWNER}/${GH_REPOSITORY}/actions/runners/registration-token \
    | jq -r '.token')

if [ "$REG_TOKEN" == "null" ] || [ -z "$REG_TOKEN" ]; then
    echo "ERROR: Registrierungstoken konnte nicht abgerufen werden. Überprüfe PAT-Berechtigungen."
    exit 1
fi

./config.sh \
    --url https://github.com/${GH_OWNER}/${GH_REPOSITORY} \
    --token "${REG_TOKEN}" \
    --name "ephemeral-$(hostname)" \
    --labels "local-highperf,ephemeral" \
    --unattended \
    --replace

cleanup() {
    echo "Job abgeschlossen. Runner wird abgemeldet..."
    REM_TOKEN=$(curl -s -X POST \
        -H "Authorization: token ${GH_PAT}" \
        -H "Accept: application/vnd.github.v3+json" \
        https://api.github.com/repos/${GH_OWNER}/${GH_REPOSITORY}/actions/runners/registration-token \
        | jq -r '.token')
    ./config.sh remove --token "${REM_TOKEN}"
}

trap 'cleanup' EXIT SIGINT SIGTERM

# --once: akzeptiert genau einen Job, führt ihn aus, dann beendet sich
./run.sh --once

Das --once-Flag ist entscheidend. Es weist den Runner-Agent an, genau einen Job anzunehmen, auszuführen und dann zu beenden. Zusammen mit einem systemd-Dienst oder Cron-Job, der den Container nach Beendigung zerstört und einen neuen vom sauberen Basis-Image startet, sorgt dies für keinen persistenten Zustand zwischen den Läufen.

Komponente 3: Der Tunnel-Agent

Mit dem laufenden Runner-Container verbindet ein Tunnel-Agent dein lokales Netzwerk mit der Steuerungsebene von GitHub. Cloudflare Tunnel (cloudflared) ist eine weitverbreitete Open-Source-Option, die eine outbound-only Verbindung herstellt, authentifiziert gegen dein Cloudflare Zero Trust-Konto. Eine äquivalente Konfiguration für frp oder andere Agents folgt demselben logischen Prinzip.

Ein minimales tunnel.yaml für eine Cloudflare Tunnel-Deployment:

tunnel: local-ci-runner-tunnel
credentials-file: /home/runner/.cloudflared/tunnel-auth.json

ingress:
  - hostname: ci-proxy.deinunternehmen.com
    service: http://localhost:8080
  - service: http_status:404

Der Hostname ci-proxy.deinunternehmen.com sollte durch Cloudflare Access-Richtlinien geschützt sein, sodass nur authentifizierter Webhook-Traffic aus den dokumentierten IP-Bereichen von GitHub durch den Tunnel geleitet werden kann. Alles andere erhält einen 404.

Das Tunnel-Token selbst sollte bei Container-Start als Umgebungsvariable übergeben werden, niemals in ein Image gebacken:

docker run --detach \
  --restart always \
  --network runner-net \
  --name cloudflared \
  cloudflare/cloudflared:latest \
  tunnel --no-autoupdate run --token "${TUNNEL_TOKEN}"

Sicherheitliche Absicherung: Die unverhandelbaren Vorgaben

Reverse Tunnels eliminieren das Problem der eingehenden Exposition, schützen aber nicht vor bösartigem Code, der innerhalb deiner Perimeter nach Beginn eines Jobs ausgeführt wird. Die folgenden Hardening-Kontrollen sind in jeder produktiven Deployment notwendig.

1. Strikte Isolationsgrenzen

Der Runner muss innerhalb eines Containers laufen, und dieser Container sollte möglichst in einer dedizierten virtuellen Maschine laufen. Firecracker-MicroVMs (genutzt von Actuated und ähnlichen Diensten) bieten hardwaregestützte Isolierung: Wenn eine Container-Exploitation durch eine Kernel-Schwachstelle erfolgt, bleibt der Angreifer in einer temporären VM ohne Zugriff auf Host-OS oder Netzwerk.

Mindestanforderungen: - Kein --privileged-Flag bei Runner-Containern - Nicht-root Nutzer im Container (wie im Dockerfile gezeigt) - Keine Volume-Mounts, die Host-Dateisystempfade exponieren - Container-Image bei jedem Job aus einem sauberen Basis-Image neu bauen; kein Image-Reuse mit Cache

2. Netzwerksegmentierung (VLAN-Sandboxing)

Der physische Server, der den Runner hostet, sollte in einem dedizierten, isolierten VLAN sitzen — z.B. in einer Entwicklungs- oder DMZ-Segment — mit Firewall-Regeln, die explizit verbieten:

  • Den Traffic vom Runner-VLAN zu internen Firmennetzwerken, Dateifreigaben und Datenbankclustern
  • Jeglichen ausgehenden Internet-Traffic außer zu den benötigten Domains: *.github.com, *.githubusercontent.com und die Endpunkte deines Tunnel-Anbieters

Dies verhindert, dass ein kompromittierter Runner für laterale Bewegungen im Netzwerk genutzt werden kann, selbst wenn die Isolationslayer von Container und VM versagen.

3. Gating untrusted Pull Requests

GitHub empfiehlt ausdrücklich, selbstgehostete Runner nicht auf öffentlichen Repositories laufen zu lassen. Jeder Contributor, der das Repository forkt und einen Pull-Request öffnet, könnte potenziell deinen Runner triggern. Für private Repositories gelten diese Kontrollen:

  • Erfordern, dass Maintainer die Ausführung von Workflows für PRs von externen Beitragsleistern oder First-Time-Forks genehmigen
  • Nutzung von Umgebungs- und Branch-Schutzregeln, sodass das local-highperf-Label nur auf geschützten Branches (main, release/*) ausgelöst werden kann
  • Der Runner gilt als vollständig untrusted Infrastruktur, die jederzeit kompromittiert werden könnte — die Architektur sollte so ausgelegt sein, dass sie auch bei Kompromittierung sicher bleibt

4. Ephemere Runner sind unverhandelbar

Persistente Runner sammeln Zustand. Ein Job, der bösartige Binärdateien zwischenspeichert, einen Rogue-Prozess laufen lässt oder Umgebungsvariablen modifiziert, kann nachfolgende Jobs vergiften, selbst bei unterschiedlichen Pull Requests. Das --once-Flag und die Destroy-and-Rebuild-Orchestrierung sind die technische Umsetzung des Ephemeral-Prinzips.

GitHub erkennt an, dass diese Kontrolle “nicht so effektiv sein könnte, wie beabsichtigt, da es keine Garantie gibt, dass ein self-hosted Runner nur einen Job ausführt.” Das ist kein Grund, sie auszulassen — es erhöht die Hürde erheblich.


Die Autoscaling-Schicht (Update 2026)

GitHub hat Anfang 2026 den Runner Scale Set Client in öffentlicher Vorschau vorgestellt — ein eigenständiges Go-Modul, das Teams ermöglicht, eigene Autoscaling-Lösungen für GitHub Actions Runners zu bauen, ohne Kubernetes zu benötigen. Das ist eine Alternative zum Actions Runner Controller (ARC), der weiterhin der empfohlene Kubernetes-basierte Autoscaling-Ansatz ist.

Für Bare-Metal-Umgebungen ohne Kubernetes erlaubt der Scale Set Client, ereignisgesteuerte Orchestrierung zu bauen, die: - auf Job-Queues via GitHub-APIs hört - auf verfügbarer Hardware einen frischen Runner-Container startet - den Container nach Abschluss des Jobs wieder zerstört

Dies bietet echte elastische Kapazität auf fester Infrastruktur — ungenutzte Hardware kostet nichts in Runner-Minuten, und Kapazität wird nur bei tatsächlichen Builds genutzt.


Preis-Check (2026)

Die Wirtschaftlichkeit selbstgehosteter Runner hat sich Anfang 2026 verändert. GitHub führte ab 1. März 2026 eine $0.002 pro Minute Plattformgebühr für die Nutzung selbstgehosteter Runner in privaten Repositories ein, die die Steuerungsebene (Job-Orchestrierung, Scheduling, Workflow-Automatisierung) abdeckt. Diese Gebühr gilt unabhängig vom Hosting-Standort — Data Center, AWS oder Bare Metal.

Öffentliche Repositories bleiben kostenlos. GitHub Enterprise Server ist nicht betroffen.

In der Praxis bedeutet das: Selbstgehostete Runner sind von GitHub aus nicht mehr kostenlos, auch wenn die Rechenkosten bei dir bei Null liegen. Für ein Team, das monatlich 3.000 Minuten über die Freigrenze hinaus nutzt, kommen $2 monatlich dazu — für die meisten Teams vernachlässigbar. Für hochvolumige CI-Umgebungen ist die Rechnung immer noch klar zugunsten Bare Metal, aber die Plattform-Gebühr sollte in die Kostenkalkulation einbezogen werden.

Auch die Preise für GitHub-gehostete Runner sind ab 1. Januar 2026 um ca. 40% gefallen, was einige Vergleiche mit Managed-Runners verändert.

Dimension GitHub-gehostete Runner Bare-Metal via Reverse Tunnel
Rechenkosten Pro Minute (ca. 40% reduziert ab Jan 2026) Feste Hardware-Kapazität; keine marginalen Kosten
Plattform-Gebühr In Runner-Preis enthalten $0.002/Min für private Repos (ab März 2026)
Eingehende Exposition Gehostet auf GitHub-Infrastruktur Keine eingehenden Ports erforderlich
Hardware-Kontrolle Standard-T-Shirt-Größen Volle Kontrolle: Apple Silicon, GPU-Cluster, FPGAs
Isolierung Managed ephemeral VMs Eigenverantwortung; erfordert explizite Hardening-Maßnahmen
Kosten für öffentliches Repo Kostenlos Kostenlos

Praxiserfahrung: Der macOS-Build-Fall

Betrachte das klassische Beispiel: Ein mobiles Entwicklungsteam, das iOS-Anwendungen kompiliert. GitHub’s M2-gestützte macOS Runner (verfügbar auf macos-latest-xlarge und ähnlichen Labels seit Ende 2025) sind deutlich schneller als frühere Cloud-macos-Optionen — aber sie laufen immer noch in einer virtualisierten Umgebung mit shared Ressourcen.

Ein ungenutztes Mac Studio mit M2 Ultra im Firmenbüro, konfiguriert als selbstgehosteter Runner via Cloudflare Tunnel, liefert native Bare-Metal-Ausführung. Die Build-Zeiten für eine typische iOS-App sinken von 30–45 Minuten auf unter 4 Minuten auf lokaler Hardware. Über ein Jahr aktiver Entwicklung summiert sich dieser Unterschied auf Tausende von Stunden eingesparter Engineering-Zeit.

Die hier beschriebene Architektur macht diese Hardware für GitHub Actions zugänglich, ohne dass Änderungen an deiner Firmenfirewall notwendig sind.


Zusammenfassung

Die Kombination aus outbound-only Reverse Tunnels, ephemeren containerisierten Runner-Umgebungen und identitätsgeprüfter Authentifizierung löst die Kernspannung zwischen Sicherheit und Performance bei selbstgehostetem CI/CD:

  • Keine eingehenden Ports. Der Runner initiiert alle Verbindungen outbound auf Port 443.
  • Keine persistenten Anmeldeinformationen auf Festplatte. Registrierungstoken werden dynamisch abgerufen und verfallen sofort.
  • Kein persistenten Zustand. Das --once-Flag und die Container-Zerstörung gewährleisten, dass jeder Job in einer sauberen Umgebung läuft.
  • Keine laterale Bewegung. VLAN-Segmentierung und explizite Firewall-Regeln begrenzen den Runner, selbst bei Kompromittierung.
  • Keine versteckten Kosten. Die Plattform-Gebühr ab 2026 ist für die meisten Teams gering, sollte aber in der Kostenplanung berücksichtigt werden.

3e Halte deine Runner-Version bei v2.329.0 oder später — GitHub begann im März 2026, ältere Versionen bei der Registrierung zu blockieren. Überprüfe die actions/runner releases page und aktualisiere den ARG RUNNER_VERSION in deinem Dockerfile entsprechend.

Die Architektur erfordert bewusste Einrichtung und laufende Wartung. Für Teams mit rechenintensiven Workloads, speziellen Hardwareanforderungen oder Sicherheitsanforderungen, die lokale Ausführung verlangen, ist sie jedoch ein gut verstandener und produktionsbewährter Weg.

Continue from this article into the most relevant product guides and workflows.

Related Topics

#self-hosted CI runner tunnel, secure GitHub Actions runner, local CI/CD hardware proxy, firewall bypass DevOps, ephemeral CI/CD runners, GitLab CI local tunnel, alternative to cloud hosted runners, self-hosted runner security 2026, on-premise build runners, bare-metal CI/CD proxy, reverse proxy for GitHub Actions, identity-gated tunnels, secure webhooks for CI/CD, connecting local hardware to GitHub, hybrid CI/CD pipeline, enterprise DevOps security, Apple Silicon CI runner, custom GPU hardware build runner, docker container build runner, ephemeral build agents, zero-trust pipeline networking, tunneling for webhooks, automated build pipeline proxy, cost-effective CI/CD scaling, infrastructure as code runner, private build network, remote build executor tunnel, self-hosted runner scaling, secure reverse tunnels, cloud infrastructure cost optimization

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles