Files
act_runner/docker-compose.yml
T
nampdnandClaude Fable 5.1 9f21d349f6 dind: split DNS -- ts.net via MagicDNS, everything else via Cloudflare
The host resolves the tailnet through MagicDNS on tailscale0 and everything
else through Cloudflare (systemd-resolved split DNS). MagicDNS has no
upstream for names outside the tailnet and answers them SERVFAIL, so
handing 100.100.100.100 to job containers as their first resolver made
every public lookup wait on a failure before falling back: image pulls
stalled and the e2e lane's *.localtest.me fetches blew a 5s poll.

A CoreDNS forwarder at a fixed address on the runner network now answers
both: ts.net is forwarded to MagicDNS, the rest to 1.1.1.1/1.0.0.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018j9YG8YFjcsVLnqzNz7LmA
2026-09-06 08:28:46 +07:00

119 lines
5.2 KiB
YAML

services:
# Split DNS for the dind daemon and its job containers: ts.net -> MagicDNS,
# everything else -> Cloudflare. See Corefile for why MagicDNS alone fails.
dns:
image: coredns/coredns@sha256:af8c8d35a5d184b386c4a6d1a012c8b218d40d1376474c7d071bb6c07201f47d
container_name: gitea-runner-dns
restart: unless-stopped
command: ["-conf", "/Corefile"]
volumes:
- ./Corefile:/Corefile:ro
networks:
runner:
ipv4_address: 172.28.0.53
# Isolated Docker daemon for the runner's job containers.
# Keeps the host Docker clean and gives reliable cache networking.
dind:
image: docker:29-dind
container_name: gitea-runner-dind
privileged: true
restart: unless-stopped
# The third --host exposes the daemon over a unix socket on a volume shared
# with the runner (dind-sock), *in addition to* its own /var/run/docker.sock.
# act_runner's job-container DooD wiring only understands unix:// (or bare)
# socket paths -- it bind-mounts that path from ITS OWN filesystem into every
# job container as /var/run/docker.sock, it does not pass DOCKER_HOST through
# as an env var. A bare "tcp://..." isn't recognized and silently falls back
# to /var/run/docker.sock, which doesn't exist in the runner container -- so
# this shared socket is what makes `docker build` etc. work inside jobs.
# Two DNS settings, for two different resolvers, and both are load-bearing.
#
# dns: (below) the dind CONTAINER's resolv.conf, which is what dockerd
# itself uses to pull images. Without an explicit list it
# inherits Docker's embedded resolver, which forwards to
# the host's systemd-resolved; with Tailscale DNS enabled
# there, public names come back SERVFAIL and every job
# dies at "docker pull" of the runner image itself. This
# is what took the runner down on 2026-09-05.
#
# --dns (here) what dockerd hands to the JOB containers it creates.
# The tailnet resolver first, so NOMAD_ADDR and the
# staging Postgres resolve inside a job (they are
# MagicDNS names); a public resolver second, because
# 100.100.100.100 answers only for the tailnet and
# SERVFAILs everything else -- getaddrinfo falls through.
#
# --default-address-pool replaces the daemon default of fifteen /16s in
# 172.17-172.31, which a CI run exhausts ("all predefined address pools
# have been fully subnetted"): the host holds three, each lane creates its
# own network, and a killed job leaves one behind. 10.210/16 in /24s is 256
# networks and collides with nothing here (no 10.x route on the host; the
# tailnet is 100.64/10).
command: ["dockerd", "--host=tcp://0.0.0.0:2375", "--host=unix:///var/run/docker.sock", "--host=unix:///dind-sock/docker.sock", "--tls=false", "--dns=172.28.0.53", "--dns=1.1.1.1", "--default-address-pool", "base=10.210.0.0/16,size=24"]
environment:
DOCKER_TLS_CERTDIR: ""
dns:
- 172.28.0.53
- 1.1.1.1
depends_on:
- dns
networks:
- runner
volumes:
# Persist pulled job images + docker layer cache => fast Rust rebuilds.
- dind-storage:/var/lib/docker
# Shared with the runner container; see the --host comment above.
- dind-sock:/dind-sock
healthcheck:
test: ["CMD", "docker", "info"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
runner:
# Ghim theo digest, KHÔNG dùng :nightly. Tag tự cập nhật + cache nằm trong
# writable layer = một lệnh 'compose pull' là mất sạch cache.
# Nâng cấp có chủ đích: đổi digest ở đây rồi commit.
image: docker.io/gitea/act_runner@sha256:7940221bcfc9df16f3401ddafb2231bf98bba5517bc381224c7a2c504b558974
container_name: gitea-runner-wsl
restart: unless-stopped
depends_on:
dind:
condition: service_healthy
networks:
- runner
environment:
GITEA_INSTANCE_URL: ${GITEA_INSTANCE_URL}
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
GITEA_RUNNER_NAME: ${GITEA_RUNNER_NAME}
GITEA_RUNNER_LABELS: ""
# Point the runner at the DinD daemon. Overridden at startup by act_runner
# itself to config.yaml's container.docker_host (the unix socket below),
# since our labels require docker; kept here as a documented fallback.
DOCKER_HOST: tcp://dind:2375
CONFIG_FILE: /config.yaml
volumes:
- ./config.yaml:/config.yaml:ro
# Persist runner registration so it survives restarts.
- runner-data:/data
# Shared with dind; see the --host comment on the dind service.
- dind-sock:/dind-sock
# Cache của actions/cache. Mặc định act_runner ghi vào
# $HOME/.cache/actcache = WRITABLE LAYER của container -> mất sạch mỗi
# lần container bị tạo lại. Phải khớp với cache.dir trong config.yaml.
- runner-cache:/cache
networks:
runner:
ipam:
config:
- subnet: 172.28.0.0/24
volumes:
dind-storage:
runner-data:
dind-sock:
runner-cache: