# vegardit/gitea-act-runner [![Build Status](https://github.com/vegardit/docker-gitea-act-runner/workflows/Build/badge.svg "GitHub Actions")](https://github.com/vegardit/docker-gitea-act-runner/actions?query=workflow%3ABuild) [![License](https://img.shields.io/github/license/vegardit/docker-gitea-act-runner.svg?label=license)](#license) [![Docker Pulls](https://img.shields.io/docker/pulls/vegardit/gitea-act-runner.svg)](https://hub.docker.com/r/vegardit/gitea-act-runner) [![Docker Stars](https://img.shields.io/docker/stars/vegardit/gitea-act-runner.svg)](https://hub.docker.com/r/vegardit/gitea-act-runner) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.1%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) 1. [What is it?](#what-is-it) 1. [Usage](#usage) 1. [License](#license) ## What is it? Multi-arch Docker image based on `debian:stable-slim` containing [Gitea](https://gitea.com)'s [act_runner](https://gitea.com/gitea/act_runner/) #### Why not using Alpine Linux? - musl-libc - Alpine's Greatest Weakness https://www.linkedin.com/pulse/musl-libc-alpines-greatest-weakness-rogan-lynch - Why I will never use Alpine Linux ever again https://martinheinz.dev/blog/92 - Does Alpine have known DNS issue within Kubernetes? https://stackoverflow.com/questions/65181012/ - Why is the Alpine Docker image over 50% slower than the Ubuntu image? https://superuser.com/questions/1219609/ - Performance issue with alpine musl library https://unix.stackexchange.com/questions/729342/ ## Usage The docker image comes in three flavors: - `vegardit/gitea-act-runner:latest`: only contains the Gitea act_runner and executes workflows in containers of the docker engine running act_runner itself (DooD / Docker-out-of-Docker approach) - `vegardit/gitea-act-runner:dind-latest`: executes workflows using an embedded docker engine (DinD / Docker-in-Docker approach) providing better process isolation - `vegardit/gitea-act-runner:dind-rootless-latest`: executes workflows using an embedded docker engine (DinD / Docker-in-Docker approach) running the docker daemon as a non-root user [(Rootless mode)](https://docs.docker.com/engine/security/rootless/) ### Docker Run Running from the command line: - Docker-out-of-Docker approach ```py docker run \ -e GITEA_INSTANCE_URL=https://gitea.example.com \ -e GITEA_RUNNER_REGISTRATION_TOKEN= \ --name gitea_act_runner \ -v /var/run/docker.sock:/var/run/docker.sock:rw \ vegardit/gitea-act-runner:latest ``` - Docker-in-Docker approach ```py docker run \ -e GITEA_INSTANCE_URL=https://gitea.example.com \ -e GITEA_RUNNER_REGISTRATION_TOKEN= \ --name gitea_act_runner \ --privileged \ vegardit/gitea-act-runner:dind-latest ``` - Docker-in-Docker approach with Docker daemon running as a non-root user (Rootless mode) ```py docker run \ -e GITEA_INSTANCE_URL=https://gitea.example.com \ -e GITEA_RUNNER_REGISTRATION_TOKEN= \ --name gitea_act_runner \ --security-opt seccomp=unconfined \ --security-opt apparmor=unconfined \ --security-opt systempaths=unconfined \ vegardit/gitea-act-runner:dind-rootless-latest ``` ### Docker Compose Example `docker-compose.yml`: - Docker-out-of-Docker approach ```yaml # https://docs.docker.com/compose/compose-file/ services: gitea_act_runner: image: vegardit/gitea-act-runner:latest #image: ghcr.io/vegardit/gitea-act-runner:latest restart: always volumes: - /var/run/docker.sock:/var/run/docker.sock:rw - /my/path/to/data/dir:/data:rw # the config file is located at /data/.runner and needs to survive container restarts environment: TZ: "Europe/Berlin" # config parameters for initial runner registration: GITEA_INSTANCE_URL: 'https://gitea.example.com' # required GITEA_RUNNER_REGISTRATION_TOKEN_FILE: 'path/to/file' # one-time registration token, only required on first container start # or: GITEA_RUNNER_REGISTRATION_TOKEN: '' ``` - Docker-in-Docker approach ```yaml # https://docs.docker.com/compose/compose-file/ services: gitea_act_runner: image: vegardit/gitea-act-runner:dind-latest #image: ghcr.io/vegardit/gitea-act-runner:dind-latest privileged: true restart: always volumes: - /my/path/to/data/dir:/data:rw # the config file is located at /data/.runner and needs to survive container restarts environment: TZ: "Europe/Berlin" # config parameters for initial runner registration: GITEA_INSTANCE_URL: 'https://gitea.example.com' # required GITEA_RUNNER_REGISTRATION_TOKEN_FILE: 'path/to/file' # one-time registration token, only required on first container start # or: GITEA_RUNNER_REGISTRATION_TOKEN: '' ``` - Docker-in-Docker rootless approach ```yaml # https://docs.docker.com/compose/compose-file/ services: gitea_act_runner: image: vegardit/gitea-act-runner:dind-rootless-latest #image: ghcr.io/vegardit/gitea-act-runner:dind-rootless-latest restart: always security_opt: - seccomp:unconfined - apparmor:unconfined - systempaths=unconfined volumes: - /my/path/to/data/dir:/data:rw # the config file is located at /data/.runner and needs to survive container restarts environment: TZ: "Europe/Berlin" # config parameters for initial runner registration: GITEA_INSTANCE_URL: 'https://gitea.example.com' # required GITEA_RUNNER_REGISTRATION_TOKEN_FILE: 'path/to/file' # one-time registration token, only required on first container start # or: GITEA_RUNNER_REGISTRATION_TOKEN: '' ``` ### Kubernetes Deployment Example deployment for Kubernetes: - Docker-out-of-Docker approach (requires access to host Docker socket) ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: gitea-act-runner spec: replicas: 1 selector: matchLabels: app: gitea-act-runner template: metadata: labels: app: gitea-act-runner spec: containers: - name: runner image: vegardit/gitea-act-runner:latest env: - name: GITEA_INSTANCE_URL value: "https://gitea.example.com" - name: GITEA_RUNNER_REGISTRATION_TOKEN valueFrom: secretKeyRef: name: gitea-runner-secret key: registration-token volumeMounts: - name: docker-sock mountPath: /var/run/docker.sock - name: data mountPath: /data volumes: - name: docker-sock hostPath: path: /var/run/docker.sock type: Socket - name: data persistentVolumeClaim: claimName: gitea-runner-pvc --- apiVersion: v1 kind: Secret metadata: name: gitea-runner-secret type: Opaque stringData: registration-token: "" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: gitea-runner-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi ``` - Docker-in-Docker approach (more secure, doesn't require host Docker access) ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: gitea-act-runner-dind spec: replicas: 1 selector: matchLabels: app: gitea-act-runner-dind template: metadata: labels: app: gitea-act-runner-dind spec: containers: - name: runner image: vegardit/gitea-act-runner:dind-latest securityContext: privileged: true env: - name: GITEA_INSTANCE_URL value: "https://gitea.example.com" - name: GITEA_RUNNER_REGISTRATION_TOKEN valueFrom: secretKeyRef: name: gitea-runner-secret key: registration-token volumeMounts: - name: data mountPath: /data - name: docker-storage mountPath: /var/lib/docker volumes: - name: data persistentVolumeClaim: claimName: gitea-runner-data-pvc - name: docker-storage emptyDir: {} ``` **Notes for Kubernetes deployments:** - For production, prefer the **DinD (rootful)** approach over DooD because it avoids exposing the host Docker socket and provides better isolation from the host. - **DinD-rootless** is generally **not recommended** in Kubernetes because: - It often requires permissive settings (`seccompProfile: Unconfined`, AppArmor `unconfined`) that many clusters disallow under their Pod Security Standards. - It provides limited additional security at the pod boundary compared to rootful DinD, while still needing relaxed sandboxing. - It has practical limitations (user-mode networking via `slirp4netns`, port publishing quirks, performance trade-offs, storage driver constraints such as `fuse-overlayfs`). - If you need "rootless" semantics, consider using **native Kubernetes Jobs** (or a Kubernetes-native runner/executor) instead of running a Docker daemon inside the pod. ### Additional environment variables The following environment variables can be specified to further configure the service. #### Runner registration: Name|Default Value|Description ----|-------------|----------- GITEA_INSTANCE_INSECURE|`false`|If `true` don't verify the TLS certificate of the Gitea instance GITEA_RUNNER_NAME|``|If not specified the container's hostname is used GITEA_RUNNER_EPHEMERAL|`false`|If `true` or `1`, the runner will exit after completing a single job GITEA_RUNNER_REGISTRATION_FILE|`/data/.runner`|The JSON file that holds the result from the runner registration with the Gitea instance GITEA_RUNNER_REGISTRATION_TIMEOUT|`30`|In case of failure, registration is retried until this timeout in seconds is reached GITEA_RUNNER_REGISTRATION_RETRY_INTERVAL|`5`|Wait period in seconds between registration retries #### Runner runtime config: Name|Default Value|Description ----|-------------|----------- GITEA_RUNNER_CONFIG_TEMPLATE_FILE|`/opt/config.template.yaml`|Template to derive the effective config file from, see [image/config.template.yaml](image/config.template.yaml) GITEA_RUNNER_UID|`1000`|The UID of the Gitea runner process GITEA_RUNNER_GID|`1000`|The GID of the Gitea runner process GITEA_RUNNER_LOG_EFFECTIVE_CONFIG|`false`|If set to true logs the effective YAML configuration to stdout during startup. #### Runner config template variables The following environment variables are referenced in the `/opt/config.template.yaml` file. Name|Default Value|Description ----|-------------|----------- GITEA_RUNNER_LABELS|``|Comma-separated list of labels in the format of `label[:schema[:args]]`.
If not specified the following labels are used
  1. `ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest`
  2. `ubuntu-24.04:docker://ghcr.io/catthehacker/ubuntu:act-24.04`
  3. `ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04`
  4. `ubuntu-20.04:docker://ghcr.io/catthehacker/ubuntu:act-20.04`
GITEA_RUNNER_LOG_LEVEL|`info`|The level of logging, can be trace, debug, info, warn, error, fatal GITEA_RUNNER_ENV_FILE|`/data/.env`|Extra environment variables to run jobs from a file GITEA_RUNNER_FETCH_TIMEOUT|`5s`|The timeout for fetching the job from the Gitea instance GITEA_RUNNER_FETCH_INTERVAL|`2s`|The interval for fetching the job from the Gitea instance GITEA_RUNNER_GITHUB_MIRROR|``| Base URL to use instead of https://github.com when fetching GitHub Actions. GITEA_RUNNER_MAX_PARALLEL_JOBS|`1`|Maximum number of concurrently executed jobs GITEA_RUNNER_JOB_CONTAINER_DOCKER_HOST|``|If empty, the available docker host is located automatically. If set to `-`, the available docker host is located automatically, but the docker host won't be mounted to the job containers. If it's any other value, the specified docker host will be used. GITEA_RUNNER_JOB_CONTAINER_NETWORK|`bridge`|Docker network to use with job containers. Can be `bridge`, `host`, `none`, or the name of a custom network GITEA_RUNNER_JOB_CONTAINER_PRIVILEGED|`false`|Whether to run jobs in containers with privileged mode which is required for **Docker-in-Docker** aka **dind** GITEA_RUNNER_JOB_CONTAINER_OPTIONS|``|Additional container launch options (eg, --add-host=my.gitea.url:host-gateway) GITEA_RUNNER_JOB_CONTAINER_WORKDIR_PARENT|`/workspace`|The parent directory of a job's working directory GITEA_RUNNER_JOB_CONTAINER_FORCE_PULL|`true`|Pull docker image(s) even if already present GITEA_RUNNER_JOB_CONTAINER_FORCE_REBUILD|`false`|Rebuild docker image(s) even if already present GITEA_RUNNER_JOB_TIMEOUT|`3h`|The maximum time a job can run before it is cancelled GITEA_RUNNER_SHUTDOWN_TIMEOUT|`0s`|The timeout for the runner to wait for running jobs to finish when shutting down GITEA_RUNNER_ENV_VAR_**N**_NAME|``|Name of the **N**-th extra environment variable to be passed to Job containers, e.g. `GITEA_RUNNER_ENV_VAR_1_NAME=MY_AUTH_TOKEN` GITEA_RUNNER_ENV_VAR_**N**_VALUE|``|Value of the **N**-th extra environment variable to be passed to Job containers, e.g. `GITEA_RUNNER_ENV_VAR_1_VALUE=SGVsbG8gbXkgZnJpZW5kIQ==` GITEA_RUNNER_VALID_VOLUME_**N**|``|Volumes (including bind mounts) that are allowed to be mounted into job containers. [Glob syntax](https://github.com/gobwas/glob) is supported, e.g. `GITEA_RUNNER_VALID_VOLUME_1=/src/*.json` GITEA_RUNNER_HOST_WORKDIR_PARENT|`/data/cache/actions`|The parent directory of a job's working directory. (Path to cache cloned actions) #### Embedded cache server: Name|Default Value|Description ----|-------------|----------- ACT_CACHE_SERVER_ENABLED|`true`| Enable the use of an embedded or external cache server with `actions/cache` in jobs ACT_CACHE_SERVER_EXTERNAL_URL|``|URL to an external cache server. If specified, act_runner will use this URL as the ACTIONS_CACHE_URL instead of starting an embedded server. The URL should end with "/". ACT_CACHE_SERVER_DIR|`/data/cache/server`| The directory to store the cache data ACT_CACHE_SERVER_HOST|``| The IP address or hostname via which the job containers can reach the cache server. Leave empty for automatic detection ACT_CACHE_SERVER_PORT|`0`|The TCP port of the cache server. `0` means to use a random, available port ## License All files in this repository are released under the [Apache License 2.0](LICENSE.txt). Individual files contain the following tag instead of the full license text: ``` SPDX-License-Identifier: Apache-2.0 ``` This enables machine processing of license information based on the SPDX License Identifiers that are available here: https://spdx.org/licenses/.