Blog

Bereitstellung selbst gehosteter GitHub-Runner auf Kubernetes (EKS) mit einem benutzerdefinierten Docker-Image

07.05.2026
Lesezeit: 7 Minuten.
Letzte Aktualisierung: 07 .05.2026

Inhaltsübersicht

Die Bereitstellung selbst gehosteter GitHub-Runner auf Organisationsebene kann komplexer sein als erwartet – insbesondere, wenn Sie vollständige Kontrolle über die Tools, die Skalierbarkeit und die Ausführungsgeschwindigkeit benötigen.

In unserem Fall benötigten wir eine maßgeschneiderte Runner-Umgebung, in der DevOps-Tools wie Terraform, AWS CLI, kubectl, Helm und Gitleaks bereits vorinstalliert waren. Die Installation dieser Tools zur Laufzeit in jedem einzelnen Workflow war ineffizient und verlangsamte die CI-Pipelines erheblich.

Die Lösung bestand darin, ein benutzerdefiniertes Docker-Image mit einem eigenen Einstiegsskript zu erstellen, kombiniert mit einer GitHub-App-basierten Authentifizierung und einer automatischen Runner-Registrierung.

Dieser Ansatz ermöglicht es den Laufprogrammen, beim Start vollständig einsatzbereit zu sein – da alle Abhängigkeiten bereits installiert sind –, wodurch der Aufwand für die Einrichtung bei jeder Ausführung des Workflows entfällt.

Die Standard-ARC-Images (Actions-Runner-Controller) bieten diesen Grad an Anpassungsmöglichkeiten nicht. Durch die Erstellung eines eigenen Images erhalten wir die volle Kontrolle über die Laufzeitumgebung und verkürzen die Ausführungszeit der CI erheblich.

In diesem Beitrag führen wir Sie durch die vollständige Einrichtung von selbst gehosteten GitHub-Runners auf Organisationsebene auf Amazon EKS unter Verwendung von ARC, einschließlich:

  • Benutzerdefiniertes Docker-Image für Runner
  • Authentifizierungsablauf der GitHub-App
  • Kubernetes-Bereitstellung über Helm
  • Automatische Skalierung entsprechend dem Arbeitsaufkommen

Stellen Sie vor dem Start sicher, dass Folgendes vorhanden ist:

  • Ein Kubernetes-Cluster (EKS, GKE, AKS oder lokal über Minikube)
  • Eine GitHub-Organisation und ein GitHub-Repository
  • Helm für die Paketverwaltung in Kubernetes installiert
  • Öffne in deiner GitHub-Organisation die Entwickler-Einstellungen und erstelle eine GitHub-App.

GitHub
  • Geben Sie der GitHub-App einen Namen:

  •  Geben Sie die URL der Startseite an

  • Deaktivieren Sie das Kontrollkästchen „Webhook-URL“

  • Berechtigungen

Für diese Demo haben wir dem „actions“- und dem „administrator“-Benutzer im Repository sowie den selbst gehosteten Runners in der Organisation Berechtigungen erteilt.
Sie können die geringstmöglichen Berechtigungen vergeben:

  • App-ID und Client-ID

  • Den privaten Schlüssel generieren

Die Installations-ID ist in der URL enthalten – speichern Sie sie für später.


In the K8 cluster run the following commands:
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm search repo cert-manager
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.17.0 \
--set prometheus.enabled=false \
--set installCRDs=true
kubectl create secret generic controller-manager -n ${NAMESPACE} \
--from-literal=github_app_id=<github_app_id> \
--from-literal=github_app_installation_id=<installation_id> \
--from-file=github_app_private_key=github.pem

helm repo add actions-runner-controller \
https://actions-runner-controller.github.io/actions-runner-controller

helm install ${ORG}-actions actions-runner-controller/actions-runner-controller \
--namespace ${NAMESPACE} \
--version 0.23.7 \
--values arc-values.yaml \
--set syncPeriod=1m

Erstellen Sie eine IAM-Rolle und ordnen Sie diese einem Kubernetes-Dienstkonto zu:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: runner-serviceaccount
  namespace: NAMESPACE
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::${accountid}:role/role-name
  • Kopiere die Docker-Datei mit dem Einstiegsskript aus meinem GitHub-Repo
    und füge nach Belieben weitere Tools hinzu.
  • Melden Sie sich bei Ihrem AWS ECR-Repo an, das wir zum Erstellen und Hochladen des benutzerdefinierten GitHub-Runner-Images verwendet haben:

aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin <account_id>.dkr.ecr.us-east-1.amazonaws.com

docker build --platform linux/arm64 -t <ecr-repo-url>:latest --push .

In unserem Fall haben wir die Anwendung für Maschinen mit arm64-Architektur kompiliert und bereitgestellt.

FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-focal

ARG TARGETOS
ARG TARGETARCH
ARG RUNNER_VERSION=2.310.2
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.3.2
ARG DOCKER_VERSION=25.0.5
ENV DEBIAN_FRONTEND=noninteractive

# Install base dependencies
RUN apt-get update -y \
    && apt-get install -y --no-install-recommends \
        sudo curl git jq unzip zip \
        build-essential locales tzdata \
        libyaml-dev tini iptables

# Create runner user
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
    && groupadd docker --gid 123 \
    && usermod -aG sudo runner \
    && usermod -aG docker runner \
    && echo "%sudo   ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers \
    && echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers

# Install GitHub Actions Runner
WORKDIR /home/runner
RUN export RUNNER_ARCH=${TARGETARCH} \
    && if [ "$RUNNER_ARCH" = "amd64" ]; then export RUNNER_ARCH=x64 ; fi \
    && curl -f -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-${TARGETOS}-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz \
    && tar xzf ./runner.tar.gz \
    && rm runner.tar.gz

# Install runner container hooks (required for k8s mode)
RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v${RUNNER_CONTAINER_HOOKS_VERSION}/actions-runner-hooks-k8s-${RUNNER_CONTAINER_HOOKS_VERSION}.zip \
    && unzip ./runner-container-hooks.zip -d ./k8s \
    && rm runner-container-hooks.zip

# Install Docker CLI and AWS CLI
RUN export RUNNER_ARCH=${TARGETARCH} \
    && if [ "$RUNNER_ARCH" = "amd64" ]; then export DOCKER_ARCH=x86_64 ; fi \
    && if [ "$RUNNER_ARCH" = "arm64" ]; then export DOCKER_ARCH=aarch64 ; fi \
    && curl -fLo docker.tgz https://download.docker.com/${TARGETOS}/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz \
    && tar zxvf docker.tgz \
    && rm -rf docker.tgz \
    && install -o root -g root -m 755 docker/* /usr/bin/ \
    && rm -rf docker \
    && curl "https://awscli.amazonaws.com/awscli-exe-linux-${DOCKER_ARCH}.zip" -o "awscliv2.zip" \
    && unzip awscliv2.zip \
    && ./aws/install

RUN mkdir /opt/hostedtoolcache \
    && chown runner:docker /opt/hostedtoolcache

#############################################
# Install your custom packages here
# Example: Terraform, Packer, kubectl, Helm, Gitleaks, etc.
# Add any tools your CI/CD pipelines need
#############################################

# Copy the custom entrypoint script
USER root
COPY scripts/entrypoint-org.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh && chown runner:runner /entrypoint.sh

VOLUME /var/lib/docker

USER runner

ENTRYPOINT ["/usr/bin/tini", "--", "/entrypoint.sh"]
CMD ["/home/runner/run.sh"]

The key idea: everything your pipelines need is baked into the image at build time. No more `apt-get install` or `curl | tar` in every workflow run.

Erstellen Sie ein Runner-Deployment-Manifest und ersetzen Sie „ECR_REPO“ durch die URL Ihres Images:



apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
	name: "self-hosted-runner"
spec:
   replicas: 2
   template:
   spec:
       image: ECR_REPO	
       imagePullPolicy: IfNotPresent	
       organization: ${ORG} # Add your GitHub organization name
    labels:
       - "my-custom-runner" should match as the lable in the entrypoint.sh 
    env:
       - name: GITHUB_ORG
       value: "${ORG}" # Add your GitHub organization name
       - name: GITHUB_APP_ID
    valueFrom:
       secretKeyRef:
       name: controller-manager
       key: github_app_id
       - name: GITHUB_INSTALLATION_ID
       valueFrom:
           secretKeyRef:
              name: controller-manager
              key: github_app_installation_id
        - name: GITHUB_PRIVATE_KEY
           valueFrom:
               secretKeyRef:
	name: controller-manager
	key: github_app_private_key



Erstellen Sie abschließend eine HPA für den Runner. Die HPA skaliert die Anzahl der Runner basierend auf den in GitHub Actions in Ihrer Organisation registrierten Jobs.

apiVersion: actions.summerwind.dev/v1alpha1
kind: HorizontalRunnerAutoscaler
metadata:
name: self-hosted-runner-hra
namespace: arc-securly-engineering
spec:
scaleDownDelaySecondsAfterScaleOut: 120
scaleTargetRef:
kind: RunnerDeployment
name: "self-hosted-runner"
minReplicas: 2
maxReplicas: 10
metrics:
- type: TotalNumberOfQueuedAndInProgressWorkflowRuns
repositoryNames:
# pass here the repo names that you want to scale on(without the org name)

Wichtiges Verhalten:

  • Hält zwei Läufer jederzeit warm
  • Skalierbar bis zu 10, je nach Arbeitslast
  • Verhindert starke Schwankungen der Skala
  • Nutzt Metriken zur GitHub-Auftragswarteschlange für Skalierungsentscheidungen

Das ist das Bindeglied, das alles zusammenhält. Das Einstiegsskript wird beim Start jedes Runner-Pods ausgeführt und übernimmt den gesamten Ablauf der GitHub-App-Authentifizierung und der Runner-Registrierung. Es sind keine Geheimnisse fest codiert – alles stammt aus Umgebungsvariablen, die über das Kubernetes-Secret bereitgestellt werden.

#!/bin/bash
set -e
GITHUB_APP_ID="${GITHUB_APP_ID}"
GITHUB_INSTALLATION_ID="${GITHUB_INSTALLATION_ID}"
GITHUB_PRIVATE_KEY="${GITHUB_PRIVATE_KEY}"
RUNNER_NAME=$HOSTNAME
GITHUB_ORG="${GITHUB_ORG}"  # Organization name

# Ensure required environment variables are set
if [[ -z "$GITHUB_APP_ID" || -z "$GITHUB_INSTALLATION_ID" || -z "$GITHUB_PRIVATE_KEY" || -z "$GITHUB_ORG" ]]; then
    echo "ERROR: Missing required GitHub App authentication variables!"
fi

RUNNER_NAME=$HOSTNAME
# Function to generate a JWT for GitHub App authentication
generate_jwt() {
    local header='{"alg":"RS256","typ":"JWT"}'
    local payload='{"iat":'$(date +%s)',"exp":'$(($(date +%s) + 600))',"iss":"'"${GITHUB_APP_ID}"'"}'

    local b64_header=$(echo -n "${header}" | openssl base64 -A | tr -d '=' | tr '/+' '_-')
    local b64_payload=$(echo -n "${payload}" | openssl base64 -A | tr -d '=' | tr '/+' '_-')

    local signature=$(echo -n "${b64_header}.${b64_payload}" | \
        openssl dgst -sha256 -sign <(echo -n "${GITHUB_PRIVATE_KEY}") | \
        openssl base64 -A | tr -d '=' | tr '/+' '_-')

    echo "${b64_header}.${b64_payload}.${signature}"
}
# Generate a JWT for GitHub App authentication
JWT=$(generate_jwt)
# Request an installation token from GitHub
INSTALLATION_TOKEN=$(curl -s -X POST \
    -H "Authorization: Bearer ${JWT}" \
    -H "Accept: application/vnd.github.v3+json" \
    "https://api.github.com/app/installations/${GITHUB_INSTALLATION_ID}/access_tokens" | jq -r .token)

if [[ -z "$INSTALLATION_TOKEN" || "$INSTALLATION_TOKEN" == "null" ]]; then
    echo "ERROR: Failed to obtain installation token"
fi

# Get the runner registration token for the organization
RUNNER_TOKEN=$(curl -s -X POST \
    -H "Authorization: token ${INSTALLATION_TOKEN}" \
    -H "Accept: application/vnd.github.v3+json" \
    "https://api.github.com/orgs/${GITHUB_ORG}/actions/runners/registration-token" | jq -r .token)

if [[ -z "$RUNNER_TOKEN" || "$RUNNER_TOKEN" == "null" ]]; then
    echo "ERROR: Failed to obtain runner registration token"
fi

# Register the runner at the organization level
./config.sh --url "https://github.com/${GITHUB_ORG}" --token "${RUNNER_TOKEN}" --unattended --replace --name "${RUNNER_NAME}" --labels my-custom-runner --ephemeral

echo "Runner registered for organization: ${GITHUB_ORG}"

# Start the runner
exec ./run.sh

Und so läuft es Schritt für Schritt ab:

  • Erstellung eines JWT mithilfe des privaten Schlüssels der GitHub-App Austausch gegen ein Installationstoken Abruf des Runner-Registrierungstokens Runner-Registrierung auf Organisationsebene Ausführung eines temporären Runners

Jeder Runner-Pod ist kurzlebig – er übernimmt einen Auftrag, führt ihn aus und wird anschließend beendet. ARC startet daraufhin einen neuen Pod, um ihn zu ersetzen.

Sobald alles bereitgestellt ist, kannst du den GitHub-Selbsthosting-Runner in deiner Organisation einsehen, indem du zu einem deiner Repositorys gehst -> Aktionen -> Runner -> Selbsthosting-Runner. 

Einsatz der Runner in Ihren Workflows:

Sobald die Runner bereitgestellt sind, kannst du sie in deinen GitHub-Actions-Workflows anhand des Labels ansprechen, das wir zuvor im Entrypoint-Skript definiert haben (`–labels my-custom-runner`). Setze in deiner Workflow-Datei einfach den Wert auf das Label, das du im Entrypoint-Skript festgelegt hast:

jobs:
  build:
    runs-on: my-custom-runner
    steps:
      - uses: actions/checkout@v4
      - run: terraform --version

Der Runner wird in der Runner-Liste Ihrer Organisation mit den Bezeichnungen „self-hosted“, „Linux“, „ARM64“ und „my-custom-runner“ angezeigt.

Durch die Kombination von:

  • Benutzerdefinierte Docker-Images
  • Authentifizierung über die GitHub-App
  • ARC auf Kubernetes
  • Autoskalierende Runner

So schaffen wir eine hochgradig skalierbare und effiziente, selbst gehostete Plattform für die CI/CD-Ausführung.

Die wichtigsten Vorteile:

  • Schnellere CI-Ausführung (keine Installationen zur Laufzeit)
  • Vollständig kontrollierte Entwicklungsumgebungen
  • Temporäre und sichere Runner
  • Skalierbare, Kubernetes-native Architektur

Weitere Blogbeiträge findest duhier.

Mehr Beiträge

Die ITGix AWS Landing Zone wird kontinuierlich weiterentwickelt, wobei ein klares Ziel im Vordergrund steht: Unternehmen sollen in die Lage versetzt werden, sichere, konforme und skalierbare AWS-Umgebungen mit geringerem Betriebsaufwand aufzubauen. In den jüngsten Versionen (v1.2.0...
Lesen
In früheren Artikeln haben wir uns mit der Migration von Repositorys aus der Bitbucket Cloud, der sicheren Aktualisierung von Projektabhängigkeiten und der Modernisierung von CI/CD-Workflows durch die Umstellung von Jenkins- und Bash-basierten Pipelines auf GitHub Actions befasst. Auf dieser Grundlage...
Lesen
Kontakt aufnehmen
ITGix bietet Ihnen fachkundige Beratung und maßgeschneiderte DevOps-Services, um Ihr Unternehmenswachstum zu beschleunigen.
Newsletter für
Technik-Experten
Schließen Sie sich 12.000+ Geschäftsführern und Ingenieuren an, die Blogs, e-Books und Fallstudien Fallstudien über neue Technologie erhalten.