Skip to main content

Gitea Runner

CI/CD act

Gitea Act Runner is the official CI runner for Gitea Actions, compatible with GitHub Actions workflow syntax. It runs workflow jobs inside Docker containers (via Docker-in-Docker). Self-hosting runners means CI jobs execute on your own hardware with access to your internal network.

Alternatives considered

Cloud Hosted

ToolOpen SourceFree TierMonthly Cost
GitHub ActionsNoLimitedFrom $0.008/min

Self Hosted

ToolOpen SourceFull FeaturesNotes
GitLab RunnerYesYesGitLab CI equivalent
JenkinsYesYesTraditional CI, significantly more complex

Installation

Architecture

  • StatefulSet: Three act_runner replicas in the gitea-runner namespace
  • Images: docker.io/gitea/act_runner:0.3.1, docker.io/docker:29.4.0-dind sidecar (both digest-pinned)
  • Storage: Per-pod Longhorn PVCs for runner data (data-gitea-runner-0/1/2) and Docker TLS certs (docker-longhorn)
  • Networking: No inbound networking; runners poll Gitea for jobs

Security

  • allowPrivilegeEscalation: false, capabilities dropped on runner container
  • Docker daemon secured with TLS mutual auth (DOCKER_HOST: tcp://docker:2376, TLS verify enabled)
  • Longhorn PVCs encrypted at rest via SOPS-managed keys

Updates

Managed by Renovate. Both images are digest-pinned.

Data Management

  • PVCs: Per-pod Longhorn-encrypted PVCs for runner state and Docker TLS certs (ephemeral by nature; no backup needed)
  • Backups: No k8up schedule — runner state is ephemeral.

User Management

Not applicable. Runners authenticate to Gitea via registration token stored in SOPS secret.

Configuration Management

  • Docker host and TLS config from ConfigMap
  • Runner registration token from SOPS-encrypted secret (shared with the Gitea app secret)

Administration

Usage

Workflows defined in Gitea repositories with .gitea/workflows/*.yaml are picked up by these runners. Jobs run inside Docker containers via the DinD sidecar. The runner registration token is stored in the Gitea secret and used to register each runner instance.

Cluster-specific deviations from the above live in the per-cluster README — see k8s/apps/talos/gitea-runner/README.md.

Cluster Deployment

Gitea Runner — Talos cluster

Cluster-specific notes only. General product info, "why we use it", and alternatives live in docusaurus/docs/apps/gitea-runner.mdx.

CI/CD act_runner nodes that execute Gitea Actions workflows.

Deviations from defaults

Defaults live in docusaurus/docs/apps/gitea-runner.mdx — document anything this cluster does differently here, with a one-line reason.

Kubernetes Metadata
  • Image: docker.io/docker:29.5.3-dind@sha256:7278248384185bcbb54c7a76b2f0a7d354c4aac920605cc1ab48968d961052fb
  • Image: docker.io/gitea/act_runner:0.6.1@sha256:b5c35d6bdbb9bb25e531230bfc7cc663cb751406cbec90a2a891b85fea54de86
Rendered manifests (kustomize build)
apiVersion: apps/v1
kind: StatefulSet
metadata:
annotations:
kustomize.toolkit.fluxcd.io/force: enabled
labels:
app: gitea-runner
name: gitea-runner
namespace: gitea-runner
spec:
replicas: 3
selector:
matchLabels:
app: gitea-runner
template:
metadata:
labels:
app: gitea-runner
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: per-worker
topologyKey: kubernetes.io/hostname
containers:
- command:
- sh
- '-c'
- |
while ! nc -z localhost 2376; do
echo 'waiting for docker daemon ...';
sleep 5;
done;
exec /sbin/tini -- run.sh
env:
- name: CONFIG_FILE
value: /actrunner/config.yaml
- name: GITEA_RUNNER_REGISTRATION_TOKEN
valueFrom:
secretKeyRef:
key: RUNNER_SECRET
name: gitea-runner
- name: GITEA_INSTANCE_URL
valueFrom:
secretKeyRef:
key: GITEA_INSTANCE_URL
name: gitea-runner
- name: DOCKER_HOST
value: tcp://127.0.0.1:2376
- name: DOCKER_TLS_VERIFY
value: '1'
- name: DOCKER_CERT_PATH
value: /certs/client
image: docker.io/gitea/act_runner:0.6.1@sha256:b5c35d6bdbb9bb25e531230bfc7cc663cb751406cbec90a2a891b85fea54de86
imagePullPolicy: IfNotPresent
name: gitea-runner
resources:
requests:
cpu: 10m
memory: 600Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- mountPath: /actrunner/config.yaml
name: config
subPath: config.yaml
- mountPath: /certs
name: docker-certs
- mountPath: /data
name: data
- args:
- '--mtu=1000'
env:
- name: DOCKER_HOST
value: tcp://127.0.0.1:2376
- name: DOCKER_TLS_VERIFY
value: '1'
- name: DOCKER_TLS_CERTDIR
value: /certs
image: docker.io/docker:29.5.3-dind@sha256:7278248384185bcbb54c7a76b2f0a7d354c4aac920605cc1ab48968d961052fb
imagePullPolicy: IfNotPresent
name: dind
resources:
requests:
cpu: 10m
memory: 500Mi
securityContext:
privileged: true
volumeMounts:
- mountPath: /certs
name: docker-certs
securityContext:
seccompProfile:
type: RuntimeDefault
volumes:
- configMap:
name: config-4649d62g84
name: config
- emptyDir: {}
name: docker-certs
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: longhorn-object-single-encrypted