We are happy to announce the release of Gitea Runner 4.0.0.
This is a runner-side release. It remains wire-compatible with existing Gitea versions; the major version bump reflects two breaking changes rather than a new server dependency.
Highlights in 4.0.0
Section titled “Highlights in 4.0.0”Built-in actions — builtin:checkout
Section titled “Built-in actions — builtin:checkout”The runner now ships native Go actions selected with uses: builtin:<name>. Jobs need no action download and no Node in their image (#871):
jobs: build: runs-on: ubuntu-latest steps: - uses: builtin:checkout - run: make testThe first built-in is builtin:checkout, a subset of actions/checkout with repository, ref, token, path, and fetch-depth. Other inputs fail the step. The runner clones shallowly on its host and copies the tree into the job; gitea-runner exec copies the local working directory for the workflow’s own ref.
Remote actions such as actions/checkout are unchanged.
Shared S3-compatible cache
Section titled “Shared S3-compatible cache”Runners can store the actions cache in an S3-compatible bucket — AWS S3, MinIO, RustFS, and the like — with no separate cache-server (#1244):
cache: s3: endpoint: "http://minio:9000" bucket: "runner-cache" prefix: "gitea-runner-cache" path_style: true access_key_file: "/run/secrets/cache-s3-access-key" secret_key_file: "/run/secrets/cache-s3-secret-key"Runners with the same settings share one cache pool. A cache saved on one runner is visible to the others within about 30 seconds. cache.s3 cannot be combined with cache.external_server; put lifecycle rules on the bucket to expire old caches and abort incomplete multipart uploads.
OpenTelemetry tracing
Section titled “OpenTelemetry tracing”Operators can send a trace of every job to an OTLP collector. Each job becomes a trace with a span per step, configured through the standard OTEL_* environment variables and off until an endpoint is set (#1206):
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318Jobs receive only TRACEPARENT, so instrumented tools inside them can join the trace without seeing the collector’s credentials. Metrics stay on the Prometheus endpoint. See docs/telemetry.md for setup and security notes.
self: action refs
Section titled “self: action refs”uses: self:{owner}/{repo}[/{path}]@{ref} resolves on the runner’s Gitea instance, so an action can refer to another repository on the same instance without hard-coding the hostname (#1233):
- uses: self:org/shared-actions/setup@v1This ships with actionslib v1.2.x: strategy.job-index and strategy.job-total come from Gitea when it sends them, and container, services, and with are evaluated once instead of on every use.
Job conditions and container options
Section titled “Job conditions and container options”A job whose if: fails to evaluate — for example fromJSON of an invalid job output — is now skipped with the error in its log instead of failing the run, matching GitHub Actions (#1247). Workflows the runner reads itself, such as with exec, are rejected when an expression does not parse.
container.options also accepts --umask (#1238). actions/cache inside composite actions no longer saves caches from failed jobs, the dind images silence the docker-init subreaper warning, and Podman runners in a container get a real Docker socket instead of an empty directory (#1242, #1241, #1231).
Breaking changes
Section titled “Breaking changes”1. Cache requests go through the runner (#1229).
Jobs no longer talk to cache.external_server or the built-in cache server directly; they send cache requests to their runner, which forwards them. Only runners need to reach cache.external_server, and that server no longer needs to reach Gitea. If jobs can reach an external cache but not their runner — typical with a remote DOCKER_HOST — set cache.host and cache.port to an address those jobs can reach. A runner container on the jobs’ Docker daemon joins each job network automatically, so those jobs can reach its cache without cache.host. Jobs on container.network: bridge cannot reach a runner outside bridge; leave container.network empty or name a user-defined network. The built-in cache has always needed job-to-runner reachability, so both modes now agree.
2. valid_volumes uses doublestar matching (#1214).
valid_volumes patterns now match with doublestar instead of gobwas/glob, which was compiled without separators so * crossed slashes. * stays within one path segment and host paths use the OS separator, which also fixes Windows patterns where a backslash was read as an escape. A host pattern that relied on * matching nested paths needs **, for example /data/** instead of /data/*. Named volumes and the bare ** are unaffected.
Upgrade notes
Section titled “Upgrade notes”- Replace
gitea/runner:3.xwithgitea/runner:4.0.0in Docker Compose, Kubernetes manifests, and service units. - If jobs use a remote Docker daemon or
container.network: bridge, review cache reachability before rolling out (breaking change 1). - Audit
valid_volumesentries that use*for nested host paths and switch them to**where needed (breaking change 2). - Optional: set
cache.s3to share a cache across runners without acache-server, trybuiltin:checkouton images without Node, or setOTEL_EXPORTER_OTLP_ENDPOINTif you want job traces in your collector. - Test artifact upload/download, cache restore, and a job on your usual network mode in a staging environment before rolling out broadly.
Download
Section titled “Download”Pre-built binaries are available from the Gitea Runner downloads page.
The release is also available from the Gitea Runner release page.
Thank you
Section titled “Thank you”Thank you to everyone who contributed code, testing, bug reports, documentation, and feedback since the 3.0.0 release.
Changelog
Section titled “Changelog”4.0.0 - 2026-09-24
Section titled “4.0.0 - 2026-09-24”-
BREAKING
-
FEATURES
- Add built-in actions framework with
builtin:checkout(#871) - Support S3-compatible storage as a shared cache backend with
cache.s3(#1244) - Add OpenTelemetry tracing via standard
OTEL_*variables (#1206) - Resolve
self:{owner}/{repo}action refs on the runner’s Gitea instance (#1233) - Support
--umaskincontainer.options(#1238)
- Add built-in actions framework with
-
BUGFIXES
- Skip jobs whose
if:fails to evaluate, matching GitHub Actions (#1247) - Stop
actions/cacheinside composite actions from saving caches of failed jobs (#1242) - Give Podman runners in a container a Docker socket instead of an empty directory (#1231)
- Silence the
docker-initsubreaper warning in the dind images (#1241) - Fix Windows paths in
valid_volumespatterns (#1214)
- Skip jobs whose
3.5.0 - 2026-09-14
Section titled “3.5.0 - 2026-09-14”-
FEATURES
-
BUGFIXES
3.4.2 - 2026-09-09
Section titled “3.4.2 - 2026-09-09”- BUGFIXES
3.4.1 - 2026-09-08
Section titled “3.4.1 - 2026-09-08”- BUGFIXES
- Stop the job’s Docker socket from becoming a directory (#1215)
3.4.0 - 2026-09-07
Section titled “3.4.0 - 2026-09-07”-
FEATURES
- Add
GITEA_DOCKER_WORKSPACEfor compose binds (#1204)
- Add
-
BUGFIXES
3.3.2 - 2026-08-31
Section titled “3.3.2 - 2026-08-31”-
FEATURES
- Add
exec --input/--input-filefor workflow_dispatch and workflow_call inputs (#1173)
- Add
-
BUGFIXES
3.3.1 - 2026-08-26
Section titled “3.3.1 - 2026-08-26”- BUGFIXES
- Keep the runner’s own
container.optionswhen privileged mode is off (#1151) - Mask secrets on every path they leave a job (#1188)
- Keep a step’s own
with:values out of itsinputscontext (#1192) - Honor volumes declared on service containers (#1186)
- Report step log ranges with the log flush (#1189)
- Fail the run when matrix expansion fails (#1187)
- Keep the runner’s own
3.3.0 - 2026-08-21
Section titled “3.3.0 - 2026-08-21”-
FEATURES
-
BUGFIXES
- Stop a failed step disabling the toolkit patch (#1177)
3.2.0 - 2026-08-19
Section titled “3.2.0 - 2026-08-19”-
FEATURES
-
BUGFIXES
3.1.0 - 2026-08-06
Section titled “3.1.0 - 2026-08-06”-
FEATURES
-
BUGFIXES
3.0.2 - 2026-08-03
Section titled “3.0.2 - 2026-08-03”- BUGFIXES
- Serve the whole results service from the cache server (#1141)
3.0.1 - 2026-08-02
Section titled “3.0.1 - 2026-08-02”- BUGFIXES
- Coerce every expression value kind to a string and drop the
format()panic (#1135)
- Coerce every expression value kind to a string and drop the

