Simplify the runner docs and fix their outdated facts (#600)

The runner pages had drifted from the runner. They claimed Podman is unsupported, a registration retry limit of 10, a `container.env` proxy override that has no effect, and they used the deprecated `generate-config` command, while the actual instructions were buried under implementation details.

The develop and 5.x pages are rewritten from the current runner sources (`main` is `v5.2.0`). They are shorter and aimed at operators, with a new page covering what jobs get. The reference generator now documents the `config` command, and the quickstart pages in every language use the `gitea-runner` binary name.

Reviewed-on: https://gitea.com/gitea/docs/pulls/600
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
This commit is contained in:
silverwind authored and silverwind committed 2026-10-21 05:25:45 +00:00
1 parent a874918bb7
commit be6aefc7e8
53 files changed
+1531 -1661

No files matched your search

@@ -4,7 +4,7 @@ sidebar_position: 1
# Job hooks
Job hooks are operator-provided scripts that run **inside the job environment**, before the job's first step and after its last one. They are the equivalent of GitHub's [job hooks](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/run-scripts):
Job hooks are scripts you provide. They run in the job environment before its first step and after its last step. Use them for setup that applies to every job, such as registry logins, mirror configuration or masking secrets used by all jobs.
```yaml
runner:
@@ -13,62 +13,34 @@ runner:
job_completed: /hooks/completed.sh
```
| Setting | Runs |
| --- | --- |
| `runner.hooks.job_started` | before the job's first step, before any action is downloaded |
| `runner.hooks.job_completed` | after the job's last post step, while the job environment is still up |
`job_started` runs before any action is downloaded. `job_completed` runs after the last post step, including when the job fails or is cancelled. If these settings are unset, the runner reads GitHub's `ACTIONS_RUNNER_HOOK_JOB_STARTED` and `ACTIONS_RUNNER_HOOK_JOB_COMPLETED` from `runner.envs` or `runner.env_file`. The runner ignores workflow values for these hook variables.
`ACTIONS_RUNNER_HOOK_JOB_STARTED` and `ACTIONS_RUNNER_HOOK_JOB_COMPLETED` are read from the runner's environment (`runner.envs`, `runner.env_file`) when the settings are unset, so a configuration carried over from `actions/runner` keeps working. The settings take precedence. A workflow cannot point the runner at a different hook: those variables are only read from the runner's own environment, never from the job's.
Both hooks are **synchronous** and block the job while they run, and a non-zero exit from either one fails the job. There is no `continue-on-error` and no per-hook timeout — the job's `runner.timeout` is the only bound. Run anything long in the background from within the hook.
Use them for per-job setup that no workflow should have to carry: registry logins, mirror configuration, or masking runner-wide secrets with `::add-mask::`.
A hook that exits with a non-zero status fails the job. Hooks block the job while they run and count against its timeout. Keep them fast.
## Where they run
The hooks run where the job's steps run: inside the job container, or on the host in host mode. The paths are resolved *there*, so the script has to exist in the job image or on the host — a path that only exists on the runner host is not visible to a containerized job. For host-wide cleanup after the job environment is gone, use the [post-task script](post-task-script.md) instead.
Hooks run inside the job container, or on the host for host jobs. GitHub always runs hooks on the host. The script must exist in the job image or on the host where it runs. Job containers cannot access a path that exists only in the runner container. To make a host directory available to jobs, mount it with `container.options` and allow it in `container.valid_volumes`.
:::note
This is a deliberate difference from `actions/runner`, which runs its job hooks on the host, outside any container the job declares. Running them where the steps run is what lets a hook prepare the environment the steps actually see.
:::
The script is run according to its extension:
| Extension | Command |
| Extension | Runs as |
| --- | --- |
| `.sh` | `bash -e <path>` |
| `.ps1` | `pwsh -command . '<path>'` |
| anything else | the file itself, which needs its own shebang and executable bit |
| `.ps1` | `pwsh -command ". '<path>'"` |
| Anything else | The file itself |
As on GitHub, the shell flags applied to `run:` steps are **not** applied to a hook — set `pipefail` or anything else you want inside the script.
The shell options of `run:` steps do not apply to hooks. Set `pipefail` and other shell options inside the script.
A hook path that does not exist inside the job environment fails the job with `No such file or directory`, naming the path.
## Environment and output
### Docker-in-Docker and Docker-out-of-Docker
A hook receives the job's environment, including `runner.envs`, the workflow's `env:` and the `GITHUB_*` variables. The runner includes hook output in the job log and masks secrets as it does for step output.
The hook is executed and its files are exchanged over the Docker API, addressed by container ID, so no path is translated between the runner and the daemon. Both setups work unchanged, but they differ in where the hook file has to be:
- **DinD** — the daemon has its own filesystem. Bake the hook into the job image; a path from the runner's filesystem is not visible to it.
- **DooD** — the job container is created by the host's daemon, so a bind mount in `container.options` is resolved against the **host**, not against the runner container. Either bake the hook into the job image, or mount a host directory and add it to `container.valid_volumes`.
## Environment
A hook sees the job's environment: the workflow, job and `container:` `env:`, the runner's `envs`, and the `GITHUB_*` context variables, with the same masking applied to its output as to a step's. The step-specific ones (`GITHUB_ACTION`, `GITHUB_OUTPUT`, `GITHUB_STATE`) are not set — a hook is not a step, so `::save-state::` and `::set-output::` have nowhere to go.
Its stdout is part of the job log, inside a collapsible group, and is scanned for workflow commands: `::add-mask::` registers a value to be masked for the rest of the job, `::set-env::` and `::add-path::` apply to the steps that follow.
`$GITHUB_ENV` and `$GITHUB_PATH` point at files that are read back after the hook exits, so the file-command form works too:
Use `$GITHUB_ENV` and `$GITHUB_PATH` to pass environment variables and paths to later steps. Mask values with `::add-mask::`:
```bash
#!/bin/bash
echo "REGISTRY_TOKEN=$(fetch-token)" >> "$GITHUB_ENV"
token="$(fetch-token)"
echo "::add-mask::$token"
echo "REGISTRY_TOKEN=$token" >> "$GITHUB_ENV"
echo "/opt/tooling/bin" >> "$GITHUB_PATH"
```
Both files are the hook's own, separate from the per-step ones, so nothing a hook writes is truncated by the first step.
## Recommendations
- Keep hooks **fast** and return the right exit code: they are on the critical path of every job, and nothing bounds them.
- Use **idempotent** operations, and expect `job_completed` to run after success, failure, and cancellation alike.
- Mask anything secret the hook prints or exports with `::add-mask::`.
For cleanup on the host after the job environment is gone, use the [post-task script](post-task-script.md).