Files
docs/runner-docs_versioned_docs/version-5/installation/kubernetes.md
T
bircni a874918bb7 Document runner 4 and 5, regenerate the 28.2.0 api documents (#599)
The 28.2.0 bump in #594 only changed the version string of the api documents, so they still listed the 28.1.0 endpoints. They are now regenerated from v28.2.0 (mostly new `423 repoArchivedError` responses).

The runner docs stopped at 3.3.0 while the runner is at 5.1.1:

- cut the runner `4` and `5` series, `/runner/` now serves 5.1.1 and `/runner/4/` 4.1.0, the `3` series is bumped to 3.5.0
- series specific image tags and versions in the new copies, the `3` series no longer claims the `latest` tag
- 4.0 and 5.0 sections in the upgrade guide, and the Git 2.34.1 requirement of 5.0 in the requirements
- regenerated every reference page from the newest stable tag of its series

Reviewed-on: https://gitea.com/gitea/docs/pulls/599
Reviewed-by: TheFox0x7 <[email protected]>
2026-10-20 19:19:28 +00:00

2.6 KiB

sidebar_position
sidebar_position
3

Install on Kubernetes

Ready-to-adapt manifests live in examples/kubernetes of the runner repository, and a Helm chart is maintained at gitea/helm-actions.

Choosing a manifest

Example Shape Docker daemon
dind-docker.yaml Deployment with a native sidecar (initContainer with restartPolicy: Always, needs Kubernetes 1.29+) privileged docker:dind sidecar, socket shared through an emptyDir
statefulset-dind.yaml StatefulSet with volumeClaimTemplates same as above
rootless-docker.yaml Deployment with a single container bundled rootless daemon of the dind-rootless image, reached over tcp://localhost:2376 with TLS

Prefer the StatefulSet variant when you scale past one replica: each pod then gets a stable identity and its own volume, so it keeps its .runner registration across restarts and reschedules instead of registering itself again as a new runner.

Two volumes, two purposes

  • /data — the runner's working directory, holding the .runner registration file and optionally the config file.
  • the daemon's data root — /var/lib/docker for the dind sidecar, /home/rootless/.local/share/docker for dind-rootless. It holds the images pulled for jobs and is not under /data. Dropping it still works, but every recreated pod re-pulls all job images.

With the rootless image, both volumes must be writable by UID/GID 1000, which is what securityContext.fsGroup: 1000 in the example is for.

Registration token

The examples read the token from a Secret:

env:
  - name: GITEA_INSTANCE_URL
    value: http://gitea-http.gitea.svc.cluster.local:3000
  - name: GITEA_RUNNER_REGISTRATION_TOKEN
    valueFrom:
      secretKeyRef:
        name: runner-secret
        key: token

The token stays valid for further registrations until it is reset, but each registration creates another runner entry. For pods that are recreated without their volume, use the instance-wide token configured on the Gitea side, and expect stale runner entries — or start an ephemeral runner per job.

Privileges

Docker-in-Docker needs securityContext.privileged: true, which lets a malicious job break out of the container. Weigh that against the alternatives:

  • the rootless flavour, which reduces but does not remove the exposure;
  • pointing the basic flavour at a daemon outside the cluster;
  • keeping such runners on a dedicated node pool and only granting them to trusted repositories.