No description
  • Go 86.2%
  • Shell 7.8%
  • Makefile 4.3%
  • HCL 1.4%
  • Dockerfile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-20 02:22:13 +00:00
.forgejo/workflows refactor: drop the admin-registrar component 2026-07-20 01:55:56 +00:00
api/v1alpha1 fix(kubernetes): match DinD bridge MTU to the pod network 2026-07-19 16:45:49 -07:00
cmd refactor: drop the admin-registrar component 2026-07-20 01:55:56 +00:00
config fix(kubernetes): match DinD bridge MTU to the pod network 2026-07-19 16:45:49 -07:00
hack fix(changelog): drop the Unreleased section and stop regenerating it 2026-06-27 09:25:22 +00:00
internal refactor: drop the admin-registrar component 2026-07-20 01:55:56 +00:00
proto/provisioner/v1 feat: scaffold Forgejo Runner Operator 2026-02-14 03:51:22 -08:00
runner-images refactor: drop the admin-registrar component 2026-07-20 01:55:56 +00:00
.gitignore feat: scaffold Forgejo Runner Operator 2026-02-14 03:51:22 -08:00
CHANGELOG.md docs(changelog): update for v0.8.3 [skip ci] 2026-07-20 02:22:13 +00:00
CLAUDE.md docs: forbid touching CHANGELOG.md unless the user asks 2026-07-19 16:45:49 -07:00
Containerfile build: pull image bases through the Harbor pull-through cache 2026-06-30 18:12:14 -07:00
Containerfile.provisioner build: pull image bases through the Harbor pull-through cache 2026-06-30 18:12:14 -07:00
go.mod chore: update x/net to 0.57.0 2026-07-15 19:42:02 -07:00
go.sum chore: update x/net to 0.57.0 2026-07-15 19:42:02 -07:00
Makefile refactor: drop the admin-registrar component 2026-07-20 01:55:56 +00:00
README.md docs: document the machine-written changelog policy 2026-06-27 02:28:10 +00:00
renovate.json feat: add CI/CD pipelines, e2e smoke test, Go 1.26 toolchain, and Renovate 2026-03-20 13:33:27 +00:00

Forgejo Runner Operator

A Kubernetes operator that manages pools of Forgejo Actions runners, supporting both in-cluster Pods (with Docker-in-Docker) and off-cluster ephemeral VMs.

Core Principle: Ephemerality Without Invisibility

Every disposable resource — a runner pod, a VM, a registration, a job execution — is also an instrumentation point. The shorter something lives, the more important it is to capture what it did while it existed.

  • Structured event log: every lifecycle transition emits a structured event to a durable store.
  • Metrics at every boundary: registration latency, job queue wait time, execution duration, VM boot time.
  • Status on the CRD: RunnerPool.status carries a full accounting including recently-destroyed runners.
  • Log forwarding from ephemeral VMs: logs ship to a collector before the runner accepts work.

Architecture

The operator consists of three components:

  1. Runner Controller (in-cluster) — reconciles RunnerPool CRDs, manages Deployments (kubernetes backend) or calls the provisioner (remote backend).
  2. Webhook Receiver (in-cluster, same binary) — receives Forgejo workflow_job webhooks for scale-to-zero support.
  3. Provisioner Agent (on VM host, separate binary) — manages ephemeral VM lifecycle via a pluggable hypervisor driver (Firecracker recommended).

Quick Start

Prerequisites

  • Go 1.23+
  • A Kubernetes cluster with kubectl configured
  • A Forgejo instance with Actions enabled

Build

# Resolve dependencies
go mod tidy

# Generate CRD manifests and deepcopy methods
make generate

# Build both binaries
make build

# Build container images
make podman-build

Install CRDs

make install

Deploy the operator

# Edit config/manager/manager.yaml to set your image
make deploy

Create a RunnerPool

# Create the namespace and token secret
kubectl create namespace forgejo-runners-trusted
kubectl create secret generic forgejo-admin-token \
  --namespace=forgejo-runners-trusted \
  --from-literal=token=<YOUR_FORGEJO_API_TOKEN>

# Apply the sample RunnerPool
kubectl apply -f config/samples/trusted_dind.yaml
# or a repo-enrolled trusted pool:
kubectl apply -f config/samples/trusted_repo_forgejo_runner_operator.yaml

For trusted runners, enroll specific projects by setting spec.forgejo.scope:

  • repository + spec.forgejo.repository: owner/repo for a single project
  • organization + spec.forgejo.organization: org for a whole org

This lets you run multiple trusted pools (for example, one per enrolled project) without exposing those runners to unrelated repositories.

Observe

# Watch the pool status
kubectl get runnerpools -A

# Detailed status including runner history
kubectl describe runnerpool trusted-dind -n forgejo-runners-trusted

# Prometheus metrics
curl http://localhost:8443/metrics | grep forgejo_runner_operator

CRD: RunnerPool

A single CRD covers both runner topologies. The backend field selects the execution strategy. Use spec.forgejo.scope (global, organization, or repository) to control which Forgejo namespace can request runners from the pool.

See config/samples/ for example manifests.

Project Layout

cmd/
  controller/       Operator entrypoint
  provisioner/      Provisioner agent entrypoint
api/v1alpha1/       CRD type definitions
internal/
  controller/       Reconciliation loop
  backend/          Execution strategy implementations
  forgejo/          Forgejo API client
  webhook/          Webhook receiver
  metrics/          Prometheus metrics
  audit/            Append-only audit logger
  provisioner/
    server/         gRPC server
    driver/         Hypervisor drivers (Firecracker, etc.)
    events/         Event ring buffer
proto/              Protobuf definitions
config/
  crd/              CRD manifests
  rbac/             RBAC resources
  manager/          Controller deployment
  samples/          Example RunnerPool manifests

Development Phases

  • Phase 0: Self-registering init container (in infra repo, no operator needed)
  • Phase 1: Operator scaffolding + in-cluster Kubernetes backend
  • Phase 2: Webhook receiver + scale-to-zero autoscaling
  • Phase 3: Provisioner agent + remote ephemeral VM backend
  • Phase 4: mTLS hardening, audit logging, Grafana dashboards

Changelog

CHANGELOG.md is intentionally machine-written and human-read: each release entry is generated from that release's commit range and committed automatically by CI — it is not hand-edited, and there is no "Unreleased" section to keep up to date. The entries are plain-language summaries for people running the operator; if you want commit-level detail, use git log or a release's compare link instead.

Because entries are generated from commits, the way to influence what shows up in the changelog is to write a good commit message — the body is summarized along with the subject, so a detailed message yields a richer entry.

License

Apache License 2.0