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.runnerregistration file and optionally the config file.- the daemon's data root —
/var/lib/dockerfor thedindsidecar,/home/rootless/.local/share/dockerfordind-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.