Skip to content

Self-hosted GitHub Actions runner

Status: Alpha Last Updated: 2026-05-30

This repository's CI workflow (.github/workflows/ci.yml) targets runners labeled self-hosted. Jobs run in parallel with isolated checkout directories (verify/, test/, lint/, e2e/) so multiple jobs on one machine do not clobber GITHUB_WORKSPACE.

  • Install multiple runner processes on the same host if you have spare CPU and memory (each registers with label self-hosted).
  • Pre-install tools below so CI skips slow apt and actions/setup-go steps (see Pre-installed tools profile).
  • Configure passwordless sudo for the runner user if you rely on CI to install missing packages (sudo apt-get, systemctl start docker). Otherwise pre-install everything in this section.
  • Give each runner its own work directory via the default GitHub Actions layout; do not share a single manual checkout path between runners.

Pre-install commands

Run on Ubuntu/Debian as a user that will run the runner service.

Build tools (make, gcc)

Required for make test, make lint, make test-e2e, and hack/verify-generated.sh.

sudo apt-get update
sudo apt-get install -y make build-essential git curl

Docker

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker "$USER"   # re-login for group membership

kubectl

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

kind

curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64
chmod +x ./kind
sudo install -o root -g root -m 0755 kind /usr/local/bin/kind

Go (1.22+)

See also local-setup.md for workstation install without sudo.

Use the official tarball (adjust version and architecture as needed):

curl -fsSL https://go.dev/dl/go1.24.4.linux-amd64.tar.gz -o /tmp/go.tar.gz
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf /tmp/go.tar.gz
echo 'export PATH=/usr/local/go/bin:$PATH' >> ~/.profile

Or install via your distribution package manager / snap if it provides Go 1.22 or newer.

Runner user and sudo

CI runs as the dedicated service user (for example github-runner on harbor). That user must not block non-interactive jobs.

If you set a password by mistake

Setting a login password with sudo passwd github-runner does not by itself break CI, but any step that runs sudo will prompt for that password and the job will fail with:

sudo: a terminal is required to read the password

Recommended — lock password login (service account):

sudo passwd -l github-runner

SSH key authentication can still work; password login is disabled.

Remove the password (empty password — not recommended on exposed hosts):

sudo passwd -d github-runner

Run once as root or with sudo:

# 1) Lock password login (optional but recommended)
sudo passwd -l github-runner

# 2) Pre-install CI tools (avoids apt in the workflow)
sudo apt-get update
sudo apt-get install -y make build-essential git curl

# 3) Docker without sudo in jobs
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker github-runner
# Restart the runner service so group membership applies, e.g.:
# sudo systemctl restart actions.runner.*.service

# 4) Optional: kind + kubectl (or let CI install to ~/.local/bin if tools are missing)

Passwordless sudo (only if you want CI to install packages)

Prefer pre-installing tools above. If CI must run apt-get or systemctl as the runner user:

sudo visudo -f /etc/sudoers.d/github-runner

Example (narrow):

github-runner ALL=(ALL) NOPASSWD: /usr/bin/apt-get, /usr/bin/systemctl, /usr/bin/service, /usr/bin/install

Broader (less secure):

github-runner ALL=(ALL) NOPASSWD: ALL

Workflow steps use sudo -n and fail with a clear error if NOPASSWD is not configured.

Add user to docker group (avoid sudo for Docker)

sudo usermod -aG docker github-runner

Restart the GitHub Actions runner service after changing groups.

CI isolation details

Job Checkout path LOCALBIN override
verify verify/ ${{ github.workspace }}/bin-${{ github.job }}
test test/ ${{ github.workspace }}/bin-test
lint lint/ (default under checkout)
e2e e2e/ ${{ github.workspace }}/bin-e2e

E2E uses a unique Kind cluster name per workflow run: vworkspace-operator-e2e-${{ github.run_id }}.

Workflow concurrency for the same branch cancels in-progress runs: group: ${{ github.workflow }}-${{ github.ref }}.

Registering a runner

Follow GitHub's self-hosted runner documentation. Use labels self-hosted and linux to match the workflow.

Pre-installed tools profile (fast CI)

When the runner host already has Go 1.22+ (matching go in go.mod), make, git, and curl, CI skips actions/setup-go and apt-get in hack/ensure-build-tools.sh. Typical time saved per job: ~30–90 seconds (Go download/extract + module cache restore; apt when tools were missing).

Runner environment variables

Configure these on the runner service (.env / systemd Environment=, or /etc/environment):

Variable Purpose
RUNNER_GO_PREINSTALLED=true Tells CI to disable actions/setup-go module cache restore/save (use when GOMODCACHE is pre-warmed on the host).
GOMODCACHE Optional shared module cache on disk (e.g. /var/cache/github-runner/gomod). If unset, the workflow uses per-job ${{ github.workspace }}/.cache/gomod-${{ github.job }}.
GOCACHE Optional shared build cache; workflow default is ${{ github.workspace }}/.cache/gocache-${{ github.job }}.

Pre-warm modules once on the host (as the runner user):

export GOMODCACHE="${GOMODCACHE:-/var/cache/github-runner/gomod}"
mkdir -p "$GOMODCACHE"
cd /path/to/vworkspace-operator
go mod download

Then set RUNNER_GO_PREINSTALLED=true on the runner process so jobs do not tarball-restore go.sum via actions/cache.

What CI checks

  • Go: go on PATH with version ≥ minimum in job go.mod (see .github/actions/setup-go-if-needed/). Otherwise falls back to actions/setup-go (same as before).
  • Build tools: hack/ensure-build-tools.sh exits immediately when make, gcc, git, and curl exist.
  • Passwordless sudo: still used only when something is missing (Docker start, kind/kubectl install, apt).

Runners without preinstalled Go behave unchanged: setup-go-if-needed runs actions/setup-go with caching enabled (unless you set RUNNER_GO_PREINSTALLED=true without pre-warming — avoid that).