# Forgejo Runner configuration - MANAGED BY ANSIBLE (roles/forgejo_runner). # Local edits are overwritten on the next run. Change the role instead. # # Until the Docker-in-Docker work this file was NOT managed: configure.yml ran # `forgejo-runner generate-config` once, on first deploy, and never touched the # result again. Setting container.docker_host meant either patching YAML in # place with a regex - the fragile thing this repo has been bitten by before - # or templating the file. So it is templated. # # Every value below was transcribed from the config.yml live on # forgejo-runner-box, which was still stock generate-config output, so applying # this with dind disabled is a no-op. Two exceptions, both deliberate: # # - runner.envs lost A_TEST_ENV_NAME_1 and A_TEST_ENV_NAME_2. They are # upstream's example placeholders and were being injected into every job on # this runner. Nothing can depend on them. # - the upstream comment blob is gone. The values are unchanged. {# Two values are assembled rather than written straight out, because each has a dind half and a caller-supplied half and they have to merge, not compete. #} {% set _envs = dict(forgejo_runner_envs) %} {% set _opts = [forgejo_runner_container_options] if forgejo_runner_container_options else [] %} {% if forgejo_runner_dind_enabled %} {% set _ = _envs.update({'DOCKER_HOST': 'tcp://' ~ forgejo_runner_dind_internal_host ~ ':' ~ forgejo_runner_dind_container_port}) %} {% set _ = _opts.insert(0, '--add-host=' ~ forgejo_runner_dind_internal_host ~ ':host-gateway') %} {% endif %} log: level: {{ forgejo_runner_log_level }} job_level: {{ forgejo_runner_log_job_level }} runner: file: .runner # Concurrent tasks. NOTE, with dind enabled: all capacity slots share ONE # Docker daemon, so two jobs running at once can see and touch each other's # containers, images and volumes through the docker CLI. Forgejo's admin docs # call this out. It is the accepted trade for a single shared runner; drop # this to 1 if untrusted code ever runs here. capacity: {{ forgejo_runner_capacity }} {% if _envs %} # Environment for every job. DOCKER_HOST is the half of the dind setup that # the JOB uses: the runner exports it into each job, service and step # container, so `docker build` and `docker compose` inside a job find the # dind daemon instead of a socket that is not there. envs: {% for key, value in _envs | dictsort %} {{ key }}: {{ value }} {% endfor %} {% else %} envs: {} {% endif %} env_file: .env timeout: {{ forgejo_runner_job_timeout }} # How long a `systemctl stop/restart` waits for in-flight jobs. It is 3h, so # redeploying while a job is running does NOT return promptly - see the # README's note on when to deploy. shutdown_timeout: {{ forgejo_runner_shutdown_timeout }} insecure: false fetch_timeout: 5s fetch_interval: 2s report_interval: 1s # Empty: the labels in .runner, fixed at registration, are used. labels: [] cache: enabled: {{ 'true' if forgejo_runner_cache_enabled else 'false' }} dir: "" # The address job containers use to reach the runner's cache server, NOT the # address it listens on. Empty means autodetect, which picks this host's # outbound address. # # This is the value most likely to need attention under dind. Job containers # now live inside dind and reach the host by NAT, so the address has to be # one that is routable from in there - this box's LAN address is, and that is # what autodetect returns today. If actions/cache starts silently missing # after enabling dind, look here first: pin forgejo_runner_cache_host to # {{ ansible_default_ipv4.address | default('this host LAN address') }}. host: "{{ forgejo_runner_cache_host }}" port: 0 proxy_port: 0 external_server: "" secret: "" actions_cache_url_override: "" container: network: "{{ forgejo_runner_container_network }}" enable_ipv6: false # Stays false. The dind CONTAINER is privileged; job containers have no need # to be, because the daemon they talk to is already running elsewhere. privileged: false {% if _opts %} # --add-host resolves the dind hostname inside every job container. Docker # expands `host-gateway` against the daemon that CREATES the container - # which is dind - so it lands on dind's own bridge gateway, where its dockerd # is listening. Without this the DOCKER_HOST above names something no job can # resolve, and jobs fail exactly as if nothing had been configured. options: '{{ _opts | join(" ") }}' {% else %} options: {% endif %} workdir_parent: valid_volumes: {{ forgejo_runner_valid_volumes | to_json }} {% if forgejo_runner_dind_enabled %} # Create job, service and step containers on the dind daemon instead of this # host's. Reached over the published loopback port, because the runner is a # plain process on the host, not a container beside dind. # # Consequence worth knowing: NOTHING a workflow does now touches the host's # Docker daemon. Images are pulled fresh into dind the first time each one is # needed, and service containers declared by a job land in dind alongside the # job container, which is what makes `docker compose` reach them by name. docker_host: 'tcp://{{ forgejo_runner_dind_bind_address }}:{{ forgejo_runner_dind_host_port }}' {% else %} # "-" - find a daemon for the runner's own use, and do not expose it to jobs. docker_host: '-' {% endif %} force_pull: false force_rebuild: false host: workdir_parent: