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. # # Allocate 256 /24 subnets instead of Docker's large default /16 and /20 # subnets. Jobs and their sibling test stacks share this daemon's pool. # The default bridge uses one slot. Interrupted cleanup can retain others; # a larger pool provides headroom but does not replace network cleanup. 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 # Longer than runner.shutdown_timeout so cancellation can clean up too. stop_grace_period: 6m 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: