mirror of
https://gitea.com/gitea/docs.git
synced 2026-09-17 19:55:34 +00:00
Add runner 2 and 3 documentations (#488)
Reviewed-on: https://gitea.com/gitea/docs/pulls/488 Reviewed-by: silverwind <[email protected]>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Install from a binary
|
||||
|
||||
The runner is a single static binary called `gitea-runner`. It has no dependencies apart from a Docker daemon for containerized jobs.
|
||||
|
||||
## Download
|
||||
|
||||
- released builds: [dl.gitea.com/gitea-runner](https://dl.gitea.com/gitea-runner/) or the [release page](https://gitea.com/gitea/runner/releases)
|
||||
- development builds of the `main` branch: [dl.gitea.com/gitea-runner/nightly](https://dl.gitea.com/gitea-runner/nightly/)
|
||||
|
||||
Each file is published next to a `.sha256` checksum and an `.xz` compressed variant:
|
||||
|
||||
```bash
|
||||
VERSION=nightly
|
||||
curl -sSLO "https://dl.gitea.com/gitea-runner/$VERSION/gitea-runner-$VERSION-linux-amd64"
|
||||
curl -sSLO "https://dl.gitea.com/gitea-runner/$VERSION/gitea-runner-$VERSION-linux-amd64.sha256"
|
||||
sha256sum -c "gitea-runner-$VERSION-linux-amd64.sha256"
|
||||
install -m 0755 "gitea-runner-$VERSION-linux-amd64" /usr/local/bin/gitea-runner
|
||||
```
|
||||
|
||||
Check that the binary matches your platform:
|
||||
|
||||
```bash
|
||||
gitea-runner --version
|
||||
```
|
||||
|
||||
## Build from source
|
||||
|
||||
Building requires the Go version declared in the repository's `go.mod`:
|
||||
|
||||
```bash
|
||||
git clone https://gitea.com/gitea/runner.git
|
||||
cd runner
|
||||
make build
|
||||
```
|
||||
|
||||
## First run
|
||||
|
||||
```bash
|
||||
gitea-runner generate-config > config.yaml # optional, defaults are safe
|
||||
gitea-runner -c config.yaml register # see "Registering a runner"
|
||||
gitea-runner -c config.yaml daemon
|
||||
```
|
||||
|
||||
The `daemon` command runs in the foreground. It reads the registration file (`runner.file`, `.runner` by default) relative to its working directory, so keep the working directory stable across restarts.
|
||||
|
||||
## Run as a systemd service
|
||||
|
||||
Create an unprivileged user, install the binary, and register the runner as that user so the `.runner` file ends up in the service's working directory:
|
||||
|
||||
```bash
|
||||
sudo useradd --system --home-dir /var/lib/gitea-runner --create-home gitea-runner
|
||||
sudo install -d /etc/gitea-runner
|
||||
sudo -u gitea-runner gitea-runner generate-config | sudo tee /etc/gitea-runner/config.yaml >/dev/null
|
||||
cd /var/lib/gitea-runner
|
||||
sudo -u gitea-runner gitea-runner register -c /etc/gitea-runner/config.yaml
|
||||
```
|
||||
|
||||
Then install the unit as `/etc/systemd/system/gitea-runner.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Gitea Actions runner
|
||||
Documentation=https://gitea.com/gitea/runner
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
# Uncomment when jobs use the local Docker daemon:
|
||||
# After=docker.service
|
||||
# Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/gitea-runner daemon --config /etc/gitea-runner/config.yaml
|
||||
WorkingDirectory=/var/lib/gitea-runner
|
||||
User=gitea-runner
|
||||
Group=gitea-runner
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
# Allow running jobs to finish before the runner is stopped. Keep this in sync
|
||||
# with runner.shutdown_timeout in the config.
|
||||
TimeoutStopSec=3h
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now gitea-runner
|
||||
```
|
||||
|
||||
If jobs use the host's Docker daemon, the `gitea-runner` user also needs access to the daemon socket. Adding it to the `docker` group grants that access and is [equivalent to root on the host](https://docs.docker.com/engine/security/#docker-daemon-attack-surface).
|
||||
|
||||
Environment variables for the process — most importantly [proxy variables](../proxy.md) — belong in `Environment=` lines or a drop-in file, not in the runner config.
|
||||
|
||||
## Run as a launchd daemon (macOS)
|
||||
|
||||
macOS uses `launchd` instead of systemd. Daemons run as `root` by default; an unprivileged `_gitea-runner` user can be created with `dscl`. Install the following as `/Library/LaunchDaemons/com.gitea.runner.plist` and adjust the paths to your installation:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.gitea.runner</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/gitea-runner</string>
|
||||
<string>daemon</string>
|
||||
<string>--config</string>
|
||||
<string>/etc/gitea-runner/config.yaml</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>/var/lib/gitea-runner</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/lib/gitea-runner/runner.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/lib/gitea-runner/runner.err</string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<key>HOME</key>
|
||||
<string>/var/lib/gitea-runner</string>
|
||||
</dict>
|
||||
<key>UserName</key>
|
||||
<string>_gitea-runner</string>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo launchctl load /Library/LaunchDaemons/com.gitea.runner.plist
|
||||
```
|
||||
|
||||
On macOS and Windows hosts, jobs usually run with [host labels](../labels.md) and the tools installed on the machine.
|
||||
|
||||
## Windows
|
||||
|
||||
Install the `windows-amd64` binary and register it as a service with any service wrapper (for example `sc.exe` plus a wrapper such as [WinSW](https://github.com/winsw/winsw), or a scheduled task at boot). The runner itself has no service-installer subcommand.
|
||||
|
||||
Keep in mind for Windows hosts:
|
||||
|
||||
- `runner.post_task_script` accepts `.exe`, `.bat` and `.cmd` paths; `.ps1` is not supported as the configured path.
|
||||
- host-mode jobs are terminated as a process tree, so tools that daemonize are not left behind.
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Install with Docker
|
||||
|
||||
The official images are published on [Docker Hub](https://hub.docker.com/r/gitea/runner/tags) as `docker.io/gitea/runner`.
|
||||
`latest` is the newest release, `nightly` is built from the `main` branch, and every release is also tagged with its version.
|
||||
|
||||
In the container the registration and the daemon are combined: the entrypoint registers the runner on first start (when no registration file exists yet) and then execs `gitea-runner daemon`.
|
||||
|
||||
## Image flavours
|
||||
|
||||
All flavours contain the same `gitea-runner` binary and differ only in how a Docker daemon is made available to jobs.
|
||||
|
||||
| Tag | Base image | Docker daemon | Supervisor | Runs as |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `nightly`, `latest`, `<version>` | `alpine` | none, you provide one | `tini` | `root` |
|
||||
| `nightly-dind`, `latest-dind` | `docker:dind` | bundled, needs `--privileged` | `s6` | `root` |
|
||||
| `nightly-dind-rootless`, `latest-dind-rootless` | `docker:dind-rootless` | bundled, rootless | `s6` | `rootless` (UID 1000) |
|
||||
|
||||
The rootless flavour's UID is fixed at 1000 by the upstream base image, and its daemon always listens on `/run/user/1000/docker.sock`, so `--user 1001` does not work. To talk to a *host* rootless daemon under another UID, use the basic flavour and bind-mount that daemon's socket instead.
|
||||
|
||||
## Basic flavour
|
||||
|
||||
The default image ships no daemon of its own, so jobs that use `docker://` images need one from outside the container — usually the host's socket:
|
||||
|
||||
```bash
|
||||
docker run -d --name my_runner \
|
||||
-e GITEA_INSTANCE_URL=<instance_url> \
|
||||
-e GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token> \
|
||||
-e GITEA_RUNNER_NAME=<runner_name> \
|
||||
-v $PWD/data:/data \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
docker.io/gitea/runner:nightly
|
||||
```
|
||||
|
||||
This flavour does not need `--privileged`. The trade-off is that jobs share the host's daemon and can therefore see its other containers and images. A job that can reach the socket can also read the reusable `GITEA_RUNNER_REGISTRATION_TOKEN` from the runner container's `docker inspect` output.
|
||||
|
||||
## Docker-in-Docker
|
||||
|
||||
The `dind` flavour bundles its own daemon, so no socket has to be mounted:
|
||||
|
||||
```bash
|
||||
docker run -d --name my_runner --privileged \
|
||||
-e GITEA_INSTANCE_URL=<instance_url> \
|
||||
-e GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token> \
|
||||
-v $PWD/data:/data \
|
||||
docker.io/gitea/runner:nightly-dind
|
||||
```
|
||||
|
||||
`s6` starts `dockerd` first and the runner service waits for it before registering. Use `nightly-dind-rootless` to run both the daemon and the runner as an unprivileged user; rootless Docker's usual limitations around networking, cgroups and storage drivers apply.
|
||||
|
||||
## Volumes
|
||||
|
||||
Two different pieces of state are worth persisting, and neither implies the other:
|
||||
|
||||
- `/data` is the runner's working directory. It holds the `.runner` registration file and, optionally, the config file. Without it, a recreated container registers itself again as a new runner, leaving a stale entry in Gitea, and fails outright if the token has been reset in the meantime.
|
||||
- the Docker daemon's data root holds the images pulled for jobs. It is **not** under `/data`: for `dind` it is `/var/lib/docker` inside the container, for `dind-rootless` it is `/home/rootless/.local/share/docker`. Give it its own volume, or every new container re-pulls the job images.
|
||||
|
||||
## Entrypoint environment variables
|
||||
|
||||
The entrypoint ([`scripts/run.sh`](https://gitea.com/gitea/runner/src/branch/main/scripts/run.sh)) understands:
|
||||
|
||||
| Variable | Meaning |
|
||||
| --- | --- |
|
||||
| `GITEA_INSTANCE_URL` | instance to register against, e.g. `https://gitea.example.com/` |
|
||||
| `GITEA_RUNNER_REGISTRATION_TOKEN` | registration token; unset before the daemon starts |
|
||||
| `GITEA_RUNNER_REGISTRATION_TOKEN_FILE` | file to read the token from, for Docker/Kubernetes secrets |
|
||||
| `GITEA_RUNNER_NAME` | runner name, defaults to the container hostname |
|
||||
| `GITEA_RUNNER_LABELS` | labels, passed to both `register` and `daemon` |
|
||||
| `GITEA_RUNNER_EPHEMERAL` | any non-empty value registers the runner as [ephemeral](../registration.md#ephemeral-runners) |
|
||||
| `GITEA_RUNNER_ONCE` | any non-empty value runs a single job, then exits |
|
||||
| `GITEA_MAX_REG_ATTEMPTS` | registration attempts before giving up, default `10` |
|
||||
| `RUNNER_STATE_FILE` | registration file name inside `/data`, default `.runner` |
|
||||
| `CONFIG_FILE` | config file inside the container, passed as `--config` |
|
||||
|
||||
These are entrypoint variables, not runner settings: the runner process itself is configured only through the [config file](../configuration.md).
|
||||
|
||||
Mount the config file when you need one:
|
||||
|
||||
```bash
|
||||
docker run -v $PWD/config.yaml:/config.yaml -e CONFIG_FILE=/config.yaml ...
|
||||
```
|
||||
|
||||
A config file can be generated with the image itself:
|
||||
|
||||
```bash
|
||||
docker run --rm --entrypoint="" docker.io/gitea/runner:nightly gitea-runner generate-config > config.yaml
|
||||
```
|
||||
|
||||
## docker compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
runner:
|
||||
image: docker.io/gitea/runner:nightly
|
||||
restart: always
|
||||
environment:
|
||||
CONFIG_FILE: /config.yaml
|
||||
GITEA_INSTANCE_URL: "${INSTANCE_URL}"
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: "${REGISTRATION_TOKEN}"
|
||||
GITEA_RUNNER_NAME: "${RUNNER_NAME}"
|
||||
GITEA_RUNNER_LABELS: "${RUNNER_LABELS}"
|
||||
volumes:
|
||||
- ./config.yaml:/config.yaml
|
||||
- ./data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
When Gitea runs in the same compose project, depend on its health check so the runner does not try to register before the instance answers:
|
||||
|
||||
```yaml
|
||||
depends_on:
|
||||
gitea:
|
||||
condition: service_healthy
|
||||
restart: true
|
||||
```
|
||||
|
||||
The rootless Docker-in-Docker variant needs a few extra options:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
runner:
|
||||
image: docker.io/gitea/runner:nightly-dind-rootless
|
||||
restart: always
|
||||
privileged: true
|
||||
security_opt:
|
||||
# for hosts running AppArmor (Ubuntu, Debian), whose default profile blocks
|
||||
# the user namespace changes the bundled daemon needs
|
||||
- apparmor=rootlesskit
|
||||
volumes:
|
||||
- ./data/runner:/data
|
||||
environment:
|
||||
- GITEA_INSTANCE_URL=<instance_url>
|
||||
- GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token>
|
||||
- DOCKER_HOST=unix:///var/run/user/1000/docker.sock
|
||||
# slirp4netns gives significantly better network throughput than vpnkit
|
||||
- DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns
|
||||
- DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=65520
|
||||
```
|
||||
|
||||
## Cache from a dockerized runner
|
||||
|
||||
A runner in a container creates a separate network per job by default, so the address it detects for its own cache server is often unreachable from job containers and `actions/cache` fails with a connection timeout. Set `cache.host` and `cache.port` explicitly and publish that port, or put the job containers on a shared network — see [Caching](../cache.md#dockerized-runners).
|
||||
|
||||
More deployment examples live in the [`examples`](https://gitea.com/gitea/runner/src/branch/main/examples) directory of the runner repository.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Install on Kubernetes
|
||||
|
||||
Ready-to-adapt manifests live in [`examples/kubernetes`](https://gitea.com/gitea/runner/src/branch/main/examples/kubernetes) of the runner repository, and a Helm chart is maintained at [gitea/helm-actions](https://gitea.com/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 `.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`:
|
||||
|
||||
```yaml
|
||||
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](../registration.md#ephemeral-runners) 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.
|
||||
Reference in New Issue
Block a user