Was ist ein Forgejo?
Forgejo ist eine beliebte, quelloffene Git-Hosting-Lösung, die sich hervorragend für Teams eignet, die ihre Infrastruktur lieber selbst verwalten. Seit Einführung von Forgejo Actions ist es möglich, CI/CD-Jobs direkt über Forgejo auszuführen – ähnlich wie bei GitHub Actions.
In diesem Beitrag zeige ich dir, wie du einen Forgejo Runner auf Kubernetes betreibst, der vollständig Docker-fähig ist und sich automatisch bei deiner Forgejo-Instanz registriert.

Inhaltsverzeichnis
Ursprung von Forgejo
Forgejo ist ein Fork von Gitea, der aus einer Auseinandersetzung innerhalb der Gitea-Community entstanden ist. Die Hintergründe sind sowohl technisch als auch organisatorisch motiviert. Hier die wichtigsten Punkte zur Entstehung und dem „Warum“:
Gitea war ursprünglich ein Community-getriebener Fork von Gogs und entwickelte sich zu einer beliebten, leichtgewichtigen Git-Hosting-Lösung in Go. Im Jahr 2022 kündigte das Gitea-Projekt an, dass eine neue Firma namens Gitea Ltd. gegründet wurde, um die Weiterentwicklung zu kommerzialisieren. Diese Entscheidung wurde von vielen Mitgliedern der Community kritisch gesehen, vor allem weil:
- Die Entscheidung ohne breiten Konsens getroffen wurde.
 - Die Kontrolle über Domain, Infrastruktur und Repositories auf die Firma überging.
 - Es Bedenken über die Offenheit und Governance des Projekts gab.
 
Als Reaktion darauf wurde Forgejo Ende 2022 als Fork ins Leben gerufen. Ziel war es, die ursprünglichen Ideale von Gitea als gemeinschaftlich entwickeltes, nicht kommerzielles Projekt wiederzubeleben.
Forgejo basiert weiterhin auf Gitea, entwickelt sich aber unabhängig und legt Wert auf:
- Community Ownership
 - Transparente Governance
 - Freiheit von kommerziellem Einfluss
 - Unterstützung durch Organisationen wie Codeberg.org, die auf freie Software setzen
 
🚀 Warum ein eigener Runner?
- Unabhängigkeit von externen Providern
 - Volle Kontrolle über CI-Umgebungen
 - Unterstützung für Docker-in-Docker Workflows
 - Optimale Integration in bestehende Kubernetes-Cluster
 
🧩 Architektur des Deployments
Namespace
apiVersion: v1kind: Namespacemetadata:  name: forgejo-runnerDeployment
apiVersion: apps/v1kind: Deploymentmetadata:  name: forgejo-runner  namespace: forgejo-runnerspec:  replicas: 2  selector:    matchLabels:      app.kubernetes.io/name: forgejo-runner      app.kubernetes.io/part-of: forgejo-runner  strategy:    type: RollingUpdate    rollingUpdate:      maxUnavailable: 1      maxSurge: 1  template:    metadata:      labels:        app.kubernetes.io/name: forgejo-runner        app.kubernetes.io/part-of: forgejo-runner    spec:      restartPolicy: Always      volumes:        - name: docker-certs          emptyDir: {{}}        - name: runner-data          emptyDir: {{}}        - name: runner-config          configMap:            name: forgejo-runner-config            items:              - key: "config.yaml"                path: "config.yaml"        - name: runner-secret          secret:            secretName: runner-secret      initContainers:        - name: runner-register          image: code.forgejo.org/forgejo/runner:7.0.0          command:            - bash            - -c            - |              set -euo pipefail              env              exec forgejo-runner register --no-interactive --token "$(cat "/runner-auth/token")" --name "$RUNNER_NAME"  "--instance" "$FORGEJO_INSTANCE_URL"          env:            - name: RUNNER_NAME              valueFrom:                fieldRef:                  fieldPath: metadata.name            - name: FORGEJO_INSTANCE_URL              value: https://git.bueraner.de          resources:            requests:              cpu: "0.25"              memory: "64Mi"            limits:              cpu: "0.50"              memory: "64Mi"          volumeMounts:            - name: runner-data              mountPath: /data            - name: runner-config              mountPath: /opt              readOnly: true            - name: runner-secret              mountPath: /runner-auth              readOnly: true      containers:        - name: runner          image: code.forgejo.org/forgejo/runner:7.0.0          command: ["sh", "-c", "while ! nc -z localhost 2376 </dev/null; do echo 'waiting for docker daemon...'; sleep 5; done; forgejo-runner daemon --config /opt/config.yaml"]          env:            - name: DOCKER_HOST              value: tcp://localhost:2376            - name: DOCKER_CERT_PATH              value: /certs/client            - name: DOCKER_TLS_VERIFY              value: "1"          volumeMounts:            - name: docker-certs              mountPath: /certs            - name: runner-data              mountPath: /data            - name: runner-config              mountPath: /opt              readOnly: true        - name: daemon          image: code.forgejo.org/oci/docker:dind          env:            - name: DOCKER_TLS_CERTDIR              value: /certs          securityContext:            privileged: true          volumeMounts:            - name: docker-certs              mountPath: /certskustomization.yaml
Ich nutze flux um meinen k3s-Cluser zu managen.
Hier wird in der kustomization.yaml die values.yaml als configMap
zur Verfügung gestellt.
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomization
resources:  - ./namespace.yaml  - ./sealed-secret.yaml  - ./deployment.yaml
configMapGenerator:  - name: forgejo-runner-config    files:      - "config.yaml"
namespace: forgejo-runnerconfig.yaml
runner:  envs:    DOCKER_HOST: tcp://localhost:2376    DOCKER_TLS_VERIFY: 1    DOCKER_CERT_PATH: /certs/client  labels:    - 'docker:docker://ghcr.io/catthehacker/ubuntu:act-22.04'    - 'self-hosted:host://-self-hosted'    - 'ubuntu-20.04:docker://ghcr.io/catthehacker/ubuntu:act-20.04'    - 'ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04'    # - 'ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest'    - 'runner-20.04:docker://ghcr.io/catthehacker/ubuntu:runner-20.04'    - 'runner-22.04:docker://ghcr.io/catthehacker/ubuntu:runner-22.04'    - 'runner-latest:docker://ghcr.io/catthehacker/ubuntu:runner-latest'container:  network: host  options: -v /certs/client:/certs/client  valid_volumes:    - /certs/client  docker_host: ""Secret
HINWEIS Das Secret runner-secret musst du natürlich noch erstellen!