runner (original) (raw)

Gitea Runner

Installation

Prerequisites

Docker Engine Community version is required for docker mode. To install Docker CE, follow the official install instructions.

Download pre-built binary

Visit here and download the right version for your platform.

Build from source

Build a docker image

Quickstart

Actions are disabled by default, so you need to add the following to the configuration file of your Gitea instance to enable it:

Register

And you will be asked to input:

  1. Gitea instance URL, like http://192.168.8.8:3000/. You should use your gitea instance ROOT_URL as the instance argument and you should not use localhost or 127.0.0.1 as instance IP;
  2. Runner token, you can get it from http://192.168.8.8:3000/admin/actions/runners;
  3. Runner name, you can just leave it blank;
  4. Runner labels, you can just leave it blank.

The process looks like:

You can also register with command line arguments.

If the registry succeed, it will run immediately. Next time, you could run the runner directly.

Run

Run with docker

Mount a volume on /data if you want the registration file and optional config to survive container recreation (see scripts/run.sh).

Image flavours

The image is published in three flavours, all built from the single multi-stage Dockerfile in this repository. They differ only in how a Docker daemon is made available to the jobs the runner executes; the gitea-runner binary inside them is identical.

Tag Build target Base image Docker daemon Process supervisor Runs as
latest (and ) basic alpine none — uses an external daemon you provide tini root
latest-dind dind docker:dind bundled, started inside the container s6 root (privileged)
latest-dind-rootless dind-rootless docker:dind-rootless bundled, started rootless inside the container s6 rootless (UID 1000)

latest — basic

The default flavour ships only the runner on a minimal Alpine base. It contains no Docker daemon of its own: jobs that use docker:// images need a daemon supplied from outside the container, typically by bind-mounting the host's socket:

tini is the entrypoint (it reaps zombie processes), and it just runs scripts/run.sh, which registers the runner on first start and then execs gitea-runner daemon. This flavour does not need --privileged. The trade-off is that jobs share the host's daemon, so they can see other containers and images on that daemon.

latest-dind — Docker-in-Docker

This flavour is based on the official docker:dind image and bundles its own Docker daemon, so it needs no external socket — only the --privileged flag that Docker-in-Docker requires:

Two processes have to run side by side here (the Docker daemon and the runner), so the entrypoint is the s6 supervision tree under scripts/s6 instead of tini. s6 starts dockerd, and the runner service waits for the daemon to come up (s6-svwait) before launching run.sh. Each container has a private daemon isolated from the host's, at the cost of running privileged.

latest-dind-rootless — rootless Docker-in-Docker

Same idea as dind, but built on docker:dind-rootless so the bundled daemon and the runner run as an unprivileged user (rootless, UID 1000) rather than root. DOCKER_HOST is preset to unix:///run/user/1000/docker.sock so the runner talks to the rootless daemon. This reduces the blast radius compared to the privileged dind flavour, but rootless Docker carries the usual rootless limitations (networking, cgroups, storage drivers, and some operations that need additional host configuration such as /etc/subuid / /etc/subgid mappings and unprivileged user-namespace support).

Note on Podman: these images target the Docker daemon. The bundled dind/dind-rootless daemons are dockerd, not Podman, and the basic flavour expects a Docker-compatible socket. Running them under rootless Podman is not a supported configuration, though pointing the basic flavour at a Podman socket that emulates the Docker API may work for some workloads.

Configuration

The runner is configured with a YAML file. Generate a starting point (this matches what ships in the tree):

Pass it with -c / --config on any command that loads configuration (register, daemon, cache-server):

Every option is described in config.example.yaml (the same content generate-config prints).

Without a config file

If you omit -c, built-in defaults apply (same as an empty YAML document). A small set of deprecated environment variables can still override parts of that default config, but only when no -c path was given; they are ignored if you use a config file:

Variable Effect
GITEA_DEBUG If true, sets log level to debug
GITEA_TRACE If true, sets log level to trace
GITEA_RUNNER_CAPACITY Concurrent jobs (integer)
GITEA_RUNNER_FILE Registration state file path (default .runner)
GITEA_RUNNER_ENVIRON Extra job env vars as comma-separated KEY:VALUE pairs
GITEA_RUNNER_ENV_FILE Path to an env file merged into job env (same idea as runner.env_file in YAML)

Prefer a YAML file for all settings.

Registration vs config labels

If runner.labels is set in the YAML file, those labels are used during register and the --labels CLI flag is ignored.

Caching (actions/cache)

Each runner starts its own cache server automatically. Cache entries are local to that runner — runners do not share a cache by default.

Shared cache across multiple runners

Run one dedicated gitea-runner cache-server that all runners point at.

  1. Create a config file for the cache server host:
  2. Start the server:
  3. On every runner:

Alternatively, mount the same NFS/CIFS share on every runner and point cache.dir at it — simpler, but with weaker isolation between repositories.

S3 / MinIO — mount object storage as a FUSE filesystem (e.g. s3fs or goofys) and set cache.dir to the mount point.

Flags --dir, --host, and --port on cache-server override the corresponding cache.* YAML keys; all other settings, including external_secret, require the config file.

Official Docker image

Besides GITEA_INSTANCE_URL and GITEA_RUNNER_REGISTRATION_TOKEN, the image entrypoint supports optional variables such as CONFIG_FILE (passed through as -c), GITEA_RUNNER_LABELS, GITEA_RUNNER_EPHEMERAL, GITEA_RUNNER_ONCE, GITEA_RUNNER_NAME, GITEA_MAX_REG_ATTEMPTS, RUNNER_STATE_FILE, and GITEA_RUNNER_REGISTRATION_TOKEN_FILE. See scripts/run.sh for exact behavior.

For a fuller container-oriented walkthrough, see examples/docker.

When container.bind_workdir is enabled, stale task workspace directories can be cleaned while the runner is idle:

Example Deployments

Check out the examples directory for sample deployment types.