Skip to main content
Version: develop

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

ExampleShapeDocker daemon
dind-docker.yamlDeployment with a native sidecar (initContainer with restartPolicy: Always, needs Kubernetes 1.29+)privileged docker:dind sidecar, socket shared through an emptyDir
statefulset-dind.yamlStatefulSet with volumeClaimTemplatessame as above
rootless-docker.yamlDeployment with a single containerbundled 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.