From 78610b01f69a540bc21ef56fd3b81b0111adc06a Mon Sep 17 00:00:00 2001 From: bircni Date: Wed, 30 Sep 2026 18:27:11 +0000 Subject: [PATCH] docs: recommend DinD for container jobs (#1258) Make Docker-in-Docker the recommended and first-shown setup. Mounting the host's Docker socket gives every job root-equivalent access to the host, while DinD gives jobs their own daemon. The default `latest` tag is unchanged. Also shorten the image flavour docs to what users need to pick an image, and correct that `dind-rootless` needs `--privileged` too. Closes https://gitea.com/gitea/runner/issues/1227 --------- Co-authored-by: silverwind <2021+silverwind@noreply.gitea.com> Co-authored-by: silverwind Reviewed-on: https://gitea.com/gitea/runner/pulls/1258 Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com> Co-authored-by: bircni --- README.md | 49 +++++++------------------------ examples/docker-compose/README.md | 11 +++---- examples/docker/README.md | 12 ++++++-- 3 files changed, 25 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 72704a94..09c17a01 100644 --- a/README.md +++ b/README.md @@ -79,53 +79,24 @@ If the registry succeed, it will run immediately. Next time, you could run the r ### Run with docker -```bash -docker run -e GITEA_INSTANCE_URL=https://your_gitea.com -e GITEA_RUNNER_REGISTRATION_TOKEN= -v /var/run/docker.sock:/var/run/docker.sock --name my_runner gitea/runner:nightly -``` - -Mount a volume on `/data` if you want the registration file and optional config to survive container recreation (see [scripts/run.sh](scripts/run.sh)). - -> **`/data` does not hold the image cache.** It is the runner's working directory and contains only the `.runner` registration file and, optionally, your config file. Images pulled for jobs live in the *Docker daemon's* data root, which for the `dind` flavours is inside the container (`/var/lib/docker`, or `/home/rootless/.local/share/docker` for `dind-rootless`). To keep the image cache across restarts, give that path its own volume as well — otherwise every new container re-pulls the job images. With the `basic` flavour the images live on whichever daemon you point the runner at, so there is nothing extra to persist. - -### Image flavours - -The image is published in three flavours, all built from the single multi-stage [Dockerfile](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`](https://github.com/krallin/tini) | `root` | -| `latest-dind` | `dind` | `docker:dind` | bundled, started inside the container | [`s6`](https://skarnet.org/software/s6/) | `root` (privileged) | -| `latest-dind-rootless` | `dind-rootless` | `docker:dind-rootless` | bundled, started rootless inside the container | [`s6`](https://skarnet.org/software/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: - -```bash -docker run -e GITEA_INSTANCE_URL=https://your_gitea.com -e GITEA_RUNNER_REGISTRATION_TOKEN= \ - -v /var/run/docker.sock:/var/run/docker.sock --name my_runner gitea/runner:latest -``` - -`tini` is the entrypoint (it reaps zombie processes), and it just runs [`scripts/run.sh`](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: +The recommended Docker-in-Docker image runs its own Docker daemon for jobs. Jobs never get the host's Docker socket, which is equivalent to root access on the host. ```bash docker run --privileged -e GITEA_INSTANCE_URL=https://your_gitea.com -e GITEA_RUNNER_REGISTRATION_TOKEN= \ - --name my_runner gitea/runner:latest-dind + -v "$PWD/data:/data" -v runner-docker:/var/lib/docker --name my_runner gitea/runner:nightly-dind ``` -Two processes have to run side by side here (the Docker daemon and the runner), so the entrypoint is the [`s6`](https://skarnet.org/software/s6/) supervision tree under [`scripts/s6`](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`](scripts/run.sh). Each container has a private daemon isolated from the host's, at the cost of running privileged. +The volumes keep the runner's registration and the job image cache across container recreation. -#### `latest-dind-rootless` — rootless Docker-in-Docker +### Image flavours -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). +| Tag | Docker daemon for jobs | +| --- | --- | +| `latest-dind` (recommended) | Bundled, needs `--privileged`. | +| `latest-dind-rootless` | Bundled, runs as a non-root user (UID 1000) for stronger isolation. Needs `--privileged` and [rootless Docker](https://docs.docker.com/engine/security/rootless/) support on the host. Keeps its image cache in `/home/rootless/.local/share/docker`. | +| `latest` | None. Mount the host's socket with `-v /var/run/docker.sock:/var/run/docker.sock`, or set `DOCKER_HOST`. | -> **The UID is fixed at 1000.** It comes from the `rootless` user baked into the upstream `docker:dind-rootless` base image, and the bundled daemon always listens on `/run/user/1000/docker.sock` inside the container, so running this flavour as a different user (`--user 1001`) does not work. If you need the runner to talk to a *host* rootless daemon that runs under some other UID, use the `basic` flavour instead and bind-mount that daemon's socket (see [examples/vm/rootless-docker.md](examples/vm/rootless-docker.md)); pointing `DOCKER_HOST` at a host socket from inside `dind-rootless` will not work. Changing the UID otherwise means rebuilding the image from a base with a different `rootless` user. - -> **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. +Versioned and `nightly` tags carry the same suffixes, for example `nightly-dind`. The images target Docker, not Podman. ### Configuration diff --git a/examples/docker-compose/README.md b/examples/docker-compose/README.md index 5bca78aa..9a0b5212 100644 --- a/examples/docker-compose/README.md +++ b/examples/docker-compose/README.md @@ -1,5 +1,7 @@ ### Running `gitea-runner` using `docker-compose` +This uses the recommended Docker-in-Docker image, see [image flavours](../../README.md#image-flavours). + ```yml ... gitea: @@ -19,8 +21,9 @@ # - GITEA_RUNNER_REGISTRATION_TOKEN= runner: - image: gitea/runner + image: gitea/runner:latest-dind restart: always + privileged: true depends_on: gitea: # required so runner can attach to gitea, see "healthcheck" @@ -28,7 +31,6 @@ restart: true volumes: - ./data/runner:/data - - /var/run/docker.sock:/var/run/docker.sock environment: - GITEA_INSTANCE_URL= # When using Docker Secrets, it's also possible to use @@ -38,10 +40,9 @@ - GITEA_RUNNER_REGISTRATION_TOKEN= ``` -### Running `gitea-runner` using Docker-in-Docker (DIND) +### Running `gitea-runner` using rootless Docker-in-Docker -- `privileged` has to be set to `true` because in-container Docker daemon requires a lot of kernel capabilities and file system mounts like `procfs` and `sysfs` -- `security_opt` sets the `apparmor` profile to `rootlesskit` for hosts running AppArmor (e.g. Ubuntu, Debian), where the kernel might otherwise block user namespace changes that Docker daemon requires for startup. The `rootlesskit` profile is provided by the `docker-ce-rootless-extras` package and is present on hosts where Docker was installed via the official installer or distro packages +On AppArmor hosts such as Ubuntu and Debian, `security_opt` is needed for the daemon to start. Docker's `docker-ce-rootless-extras` package provides the `rootlesskit` profile. ```yml ... diff --git a/examples/docker/README.md b/examples/docker/README.md index 51c14f51..58c1a96c 100644 --- a/examples/docker/README.md +++ b/examples/docker/README.md @@ -1,8 +1,14 @@ ### Run `gitea-runner` in a Docker Container +The recommended Docker-in-Docker image runs its own Docker daemon for jobs, so they never get the host's Docker socket: + ```sh -docker run -e GITEA_INSTANCE_URL=http://192.168.8.18:3000 -e GITEA_RUNNER_REGISTRATION_TOKEN= -v /var/run/docker.sock:/var/run/docker.sock -v $PWD/data:/data --name my_runner gitea/runner:nightly +docker run --privileged \ + -e GITEA_INSTANCE_URL=http://192.168.8.18:3000 \ + -e GITEA_RUNNER_REGISTRATION_TOKEN= \ + -v "$PWD/data:/data" \ + -v runner-docker:/var/lib/docker \ + --name my_runner gitea/runner:nightly-dind ``` -The `/data` directory inside the docker container contains the runner API keys after registration. -It must be persisted, otherwise the runner would try to register again, using the same, now defunct registration token. +The volumes keep the runner's registration and the job image cache. See [image flavours](../../README.md#image-flavours) for alternatives.