<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Lunatic's Blog</title><link>https://blog.lunatic.ren/</link><description>Recent content on Lunatic's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Thu, 13 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.lunatic.ren/index.xml" rel="self" type="application/rss+xml"/><item><title>单机 Forgejo Actions 跑 docker build：job 必须进独立 DinD，网络还得是 host</title><link>https://blog.lunatic.ren/p/forgejo-actions-dind/</link><pubDate>Thu, 13 Aug 2026 00:00:00 +0000</pubDate><guid>https://blog.lunatic.ren/p/forgejo-actions-dind/</guid><description>&lt;h1 id="单机-forgejo-actions-跑-docker-buildjob-必须进独立-dind网络还得是-host"&gt;单机 Forgejo Actions 跑 &lt;code&gt;docker build&lt;/code&gt;：job 必须进独立 DinD，网络还得是 &lt;code&gt;host&lt;/code&gt;&#10;&lt;/h1&gt;&lt;p&gt;这台机器上 Forgejo Actions 能跑 &lt;code&gt;docker build&lt;/code&gt;，靠的是三个值对齐：job 容器进独立的 &lt;code&gt;docker:dind&lt;/code&gt;、&lt;code&gt;container.network&lt;/code&gt; 写成 &lt;code&gt;host&lt;/code&gt;、&lt;code&gt;DOCKER_HOST&lt;/code&gt; 指向 &lt;code&gt;tcp://dind:2375&lt;/code&gt;。少写其中任何一个，CI 不是变慢，是 checkout 或 &lt;code&gt;docker&lt;/code&gt; 直接断。下面从这台机器上正在跑的真实配置拆起，说清楚每一层为什么这样写。&lt;/p&gt;&#10;&lt;h2 id="forgejo-actions-实际在干什么"&gt;Forgejo Actions 实际在干什么&#10;&lt;/h2&gt;&lt;p&gt;Forgejo 自己不执行 CI。浏览器里点到的是 Forgejo，&lt;code&gt;.forgejo/workflows/*.yml&lt;/code&gt; 推上去之后，它只负责任务落库、等 runner 来领。领到任务的进程叫 &lt;code&gt;forgejo-runner&lt;/code&gt;，它再用 Docker API 起一个 job 容器，把步骤丢进去跑。所以这条链路有三层：Forgejo（Git 服务加 Actions 队列）、runner（常驻进程，轮询任务、调 Docker）、执行环境（真正跑 &lt;code&gt;run:&lt;/code&gt; 的容器）。第三层才是这篇文章要讲清楚的部分。&lt;/p&gt;&#10;&lt;p&gt;执行环境可以是宿主机 Docker，也可以是另一套 daemon。把宿主机 &lt;code&gt;/var/run/docker.sock&lt;/code&gt; 挂进 job，workflow 里的 &lt;code&gt;docker build&lt;/code&gt; 立刻能用，代价也立刻到位：这条 YAML 等价于这台机器的 root。能起容器，就能挂 &lt;code&gt;/&lt;/code&gt;，就能把旁边的 Postgres、反代、Forgejo 数据盘一起摸一遍。单机自托管最容易在这里省事，这台机器没有省。&lt;/p&gt;&#10;&lt;h2 id="这台机器上的四只容器和两张网"&gt;这台机器上的四只容器和两张网&#10;&lt;/h2&gt;&lt;p&gt;当前 &lt;code&gt;docker ps&lt;/code&gt; 里和这条链路有关的是 &lt;code&gt;forgejo&lt;/code&gt;（&lt;code&gt;forgejo:15&lt;/code&gt;，Up 2 days）、&lt;code&gt;forgejo_db&lt;/code&gt;（&lt;code&gt;postgres:17-alpine&lt;/code&gt;，Up 2 days）、&lt;code&gt;forgejo-runner&lt;/code&gt;（&lt;code&gt;runner:12&lt;/code&gt;，Up 32 hours）、&lt;code&gt;dind&lt;/code&gt;（&lt;code&gt;docker:dind&lt;/code&gt;，Up 32 hours，healthy）。两张外部桥接网络：&lt;code&gt;network_bus&lt;/code&gt; 给反代侧，Forgejo 和 runner 在这张网上；&lt;code&gt;forgejo_runner&lt;/code&gt; 给 CI 内部互访，四只容器都在，Postgres 只在这张网上，不进 &lt;code&gt;network_bus&lt;/code&gt;。Forgejo 和 runner 同时挂在这两张网上，是故意的：控制面走 &lt;code&gt;forgejo_runner&lt;/code&gt;，对外流量走 &lt;code&gt;network_bus&lt;/code&gt;。&lt;/p&gt;&#10;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR&#10; subgraph host["宿主机 Docker"]&#10; FJ[forgejo:15]&#10; DB[forgejo_db]&#10; RN[forgejo-runner:12]&#10; DIND["dind&lt;br/&gt;privileged / 2375"]&#10; end&#10;&#10; RP[反代] --&gt;|127.0.0.1:3000| FJ&#10; RN --&gt;|领任务 http://forgejo:3000| FJ&#10; RN --&gt;|Docker API tcp://dind:2375| DIND&#10; DB --- FJ&#10;&#10; subgraph dindnet["DinD 里的 job"]&#10; JOB["job 容器&lt;br/&gt;network=host"]&#10; end&#10;&#10; DIND --&gt; JOB&#10; JOB --&gt;|clone / cache| FJ&#10; JOB --&gt;|DOCKER_HOST=tcp://dind:2375| DIND&lt;/pre&gt;&lt;p&gt;Runner 向 DinD 要容器，job 跑在 DinD 里，不跑在宿主机上。workflow 里的 &lt;code&gt;docker&lt;/code&gt; 客户端连的也是 DinD，不是宿主机。验证方法在最后一段：&lt;code&gt;docker info&lt;/code&gt; 的 &lt;code&gt;Name&lt;/code&gt; 应该是 &lt;code&gt;dind&lt;/code&gt;，不是宿主机名。&lt;/p&gt;&#10;&lt;h2 id="forgejo-这边只要三件事"&gt;Forgejo 这边只要三件事&#10;&lt;/h2&gt;&lt;p&gt;compose 按官方 Docker 安装来：应用和库拆开、UID 1000、数据盘本地目录。下面是这台机器 &lt;code&gt;forgejo/compose.yml&lt;/code&gt; 的完整内容，密码换成自己的。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;span class="lnt"&gt;18&#10;&lt;/span&gt;&lt;span class="lnt"&gt;19&#10;&lt;/span&gt;&lt;span class="lnt"&gt;20&#10;&lt;/span&gt;&lt;span class="lnt"&gt;21&#10;&lt;/span&gt;&lt;span class="lnt"&gt;22&#10;&lt;/span&gt;&lt;span class="lnt"&gt;23&#10;&lt;/span&gt;&lt;span class="lnt"&gt;24&#10;&lt;/span&gt;&lt;span class="lnt"&gt;25&#10;&lt;/span&gt;&lt;span class="lnt"&gt;26&#10;&lt;/span&gt;&lt;span class="lnt"&gt;27&#10;&lt;/span&gt;&lt;span class="lnt"&gt;28&#10;&lt;/span&gt;&lt;span class="lnt"&gt;29&#10;&lt;/span&gt;&lt;span class="lnt"&gt;30&#10;&lt;/span&gt;&lt;span class="lnt"&gt;31&#10;&lt;/span&gt;&lt;span class="lnt"&gt;32&#10;&lt;/span&gt;&lt;span class="lnt"&gt;33&#10;&lt;/span&gt;&lt;span class="lnt"&gt;34&#10;&lt;/span&gt;&lt;span class="lnt"&gt;35&#10;&lt;/span&gt;&lt;span class="lnt"&gt;36&#10;&lt;/span&gt;&lt;span class="lnt"&gt;37&#10;&lt;/span&gt;&lt;span class="lnt"&gt;38&#10;&lt;/span&gt;&lt;span class="lnt"&gt;39&#10;&lt;/span&gt;&lt;span class="lnt"&gt;40&#10;&lt;/span&gt;&lt;span class="lnt"&gt;41&#10;&lt;/span&gt;&lt;span class="lnt"&gt;42&#10;&lt;/span&gt;&lt;span class="lnt"&gt;43&#10;&lt;/span&gt;&lt;span class="lnt"&gt;44&#10;&lt;/span&gt;&lt;span class="lnt"&gt;45&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# forgejo/compose.yml，密码不要抄，自己换&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;network_bus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;external&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;forgejo_runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;external&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;codeberg.org/forgejo/forgejo:15&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;forgejo&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;USER_UID=1000&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;USER_GID=1000&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FORGEJO__database__DB_TYPE=postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FORGEJO__database__HOST=db:5432&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FORGEJO__database__NAME=forgejo_db&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FORGEJO__database__USER=forgejo_user&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FORGEJO__database__PASSWD=change-me&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;network_bus&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;forgejo_runner&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./forgejo:/data&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/etc/timezone:/etc/timezone:ro&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;127.0.0.1:3000:3000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;22:22&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;db&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres:17-alpine&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;forgejo_db&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_USER=forgejo_user&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_PASSWORD=change-me&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_DB=forgejo_db&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;forgejo_runner&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./postgres:/var/lib/postgresql/data&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;三件事，每一件都有具体原因。&lt;/p&gt;&#10;&lt;p&gt;HTTP 不直接暴露。&lt;code&gt;127.0.0.1:3000:3000&lt;/code&gt;，公网只走反代。&lt;code&gt;app.ini&lt;/code&gt; 里 &lt;code&gt;ROOT_URL&lt;/code&gt; 必须是外网看到的那个 &lt;code&gt;https://forgejo.lunatic.ren/&lt;/code&gt;，clone 链接、webhook、OIDC 回调都吃这个值。SSH 单独处理，这台把 &lt;code&gt;22:22&lt;/code&gt; 直接映射出去了，因为 Git over SSH 不想再套一层反代。&lt;/p&gt;&#10;&lt;p&gt;Runner 必须能用容器名找到 Forgejo，所以两台都进 &lt;code&gt;forgejo_runner&lt;/code&gt;。runner 的 &lt;code&gt;.env&lt;/code&gt; 里写 &lt;code&gt;FORGEJO_INSTANCE_URL=http://forgejo:3000&lt;/code&gt;，不是那个 HTTPS 域名。这是故意的：runner 和 Forgejo 同机、同 Docker 网络，走容器 DNS 比绕公网证书、反代、Hairpin NAT 省事。用户浏览器走 HTTPS，CI 内部控制面走内网 HTTP，两套 URL 各管各的，别合成一个再在 TLS 上熬。&lt;/p&gt;&#10;&lt;p&gt;数据目录属主是 1000，容器里对应 &lt;code&gt;git&lt;/code&gt; 用户。恢复备份时忘了 &lt;code&gt;chown -R 1000:1000&lt;/code&gt;，Forgejo 起来直接报权限。这台用 &lt;code&gt;forgejo dump&lt;/code&gt; 打一致性快照，本地留两份，每月一份进 COS 深度归档。站点级 Actions 在 Forgejo 1.21 之后默认开启，&lt;code&gt;app.ini&lt;/code&gt; 里不需要显式写 &lt;code&gt;[actions]&lt;/code&gt;；要关才需要 &lt;code&gt;ENABLED = false&lt;/code&gt;。仓库级还要在仓库设置里打开 Actions，否则 runner 在线也领不到这个仓的任务。&lt;/p&gt;&#10;&lt;h2 id="runner-不跑-job它只创建-job"&gt;Runner 不跑 job，它只创建 job&#10;&lt;/h2&gt;&lt;p&gt;官方安装文档给的 Compose 就是 runner 容器加 &lt;code&gt;docker:dind&lt;/code&gt;，这台跟这个结构，没发明新拓扑。但真实文件里有几个点值得逐字拆开，包括两个官方模板自带的坑。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;span class="lnt"&gt;18&#10;&lt;/span&gt;&lt;span class="lnt"&gt;19&#10;&lt;/span&gt;&lt;span class="lnt"&gt;20&#10;&lt;/span&gt;&lt;span class="lnt"&gt;21&#10;&lt;/span&gt;&lt;span class="lnt"&gt;22&#10;&lt;/span&gt;&lt;span class="lnt"&gt;23&#10;&lt;/span&gt;&lt;span class="lnt"&gt;24&#10;&lt;/span&gt;&lt;span class="lnt"&gt;25&#10;&lt;/span&gt;&lt;span class="lnt"&gt;26&#10;&lt;/span&gt;&lt;span class="lnt"&gt;27&#10;&lt;/span&gt;&lt;span class="lnt"&gt;28&#10;&lt;/span&gt;&lt;span class="lnt"&gt;29&#10;&lt;/span&gt;&lt;span class="lnt"&gt;30&#10;&lt;/span&gt;&lt;span class="lnt"&gt;31&#10;&lt;/span&gt;&lt;span class="lnt"&gt;32&#10;&lt;/span&gt;&lt;span class="lnt"&gt;33&#10;&lt;/span&gt;&lt;span class="lnt"&gt;34&#10;&lt;/span&gt;&lt;span class="lnt"&gt;35&#10;&lt;/span&gt;&lt;span class="lnt"&gt;36&#10;&lt;/span&gt;&lt;span class="lnt"&gt;37&#10;&lt;/span&gt;&lt;span class="lnt"&gt;38&#10;&lt;/span&gt;&lt;span class="lnt"&gt;39&#10;&lt;/span&gt;&lt;span class="lnt"&gt;40&#10;&lt;/span&gt;&lt;span class="lnt"&gt;41&#10;&lt;/span&gt;&lt;span class="lnt"&gt;42&#10;&lt;/span&gt;&lt;span class="lnt"&gt;43&#10;&lt;/span&gt;&lt;span class="lnt"&gt;44&#10;&lt;/span&gt;&lt;span class="lnt"&gt;45&#10;&lt;/span&gt;&lt;span class="lnt"&gt;46&#10;&lt;/span&gt;&lt;span class="lnt"&gt;47&#10;&lt;/span&gt;&lt;span class="lnt"&gt;48&#10;&lt;/span&gt;&lt;span class="lnt"&gt;49&#10;&lt;/span&gt;&lt;span class="lnt"&gt;50&#10;&lt;/span&gt;&lt;span class="lnt"&gt;51&#10;&lt;/span&gt;&lt;span class="lnt"&gt;52&#10;&lt;/span&gt;&lt;span class="lnt"&gt;53&#10;&lt;/span&gt;&lt;span class="lnt"&gt;54&#10;&lt;/span&gt;&lt;span class="lnt"&gt;55&#10;&lt;/span&gt;&lt;span class="lnt"&gt;56&#10;&lt;/span&gt;&lt;span class="lnt"&gt;57&#10;&lt;/span&gt;&lt;span class="lnt"&gt;58&#10;&lt;/span&gt;&lt;span class="lnt"&gt;59&#10;&lt;/span&gt;&lt;span class="lnt"&gt;60&#10;&lt;/span&gt;&lt;span class="lnt"&gt;61&#10;&lt;/span&gt;&lt;span class="lnt"&gt;62&#10;&lt;/span&gt;&lt;span class="lnt"&gt;63&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# forgejo_runner/compose.yaml&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker:dind&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;dind&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;dind &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# job 容器用 host 网络时，DOCKER_HOST=tcp://dind:2375 靠这个解析&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;privileged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;DOCKER_TLS_CERTDIR= &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 关掉 TLS，走明文 2375（仅内网 docker 网络可达）&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CMD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;docker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;info&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;5s&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;5s&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./dind-data:/var/lib/docker &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 持久化，dind 重建不丢镜像缓存&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;forgejo_runner&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;forgejo_runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;code.forgejo.org/forgejo/runner:12&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${CONTAINER_NAME:-forgejo-runner}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;service_healthy&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;30033:${DOCKER_GID:-999}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;DOCKER_HOST=tcp://dind:2375&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FORGEJO_TOKEN=&amp;lt;redacted&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FORGEJO_INSTANCE_URL=${FORGEJO_INSTANCE_URL}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;RUNNER_REGISTRATION_TOKEN=${RUNNER_REGISTRATION_TOKEN}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;RUNNER_NAME=${RUNNER_NAME:-default-runner}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;RUNNER_LABELS=${RUNNER_LABELS:-docker:docker://node:20-bookworm}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; /bin/sh -c &amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; cd /data &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; chown -R 1000:1000 /data 2&amp;gt;/dev/null || true &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; if [ ! -s .runner ]; then&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;&amp;gt;&amp;gt;&amp;gt; Registering runner...&amp;#34; &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; forgejo-runner register --no-interactive \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --instance ${FORGEJO_INSTANCE_URL} \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --token ${RUNNER_REGISTRATION_TOKEN} \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --name ${RUNNER_NAME} \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --labels ${RUNNER_LABELS} &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; forgejo-runner generate-config &amp;gt; config.yml;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; fi &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;&amp;gt;&amp;gt;&amp;gt; Starting daemon...&amp;#34; &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; forgejo-runner --config config.yml daemon&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./data:/data&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;network_bus&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;forgejo_runner&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;network_bus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;external&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;forgejo_runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;external&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;code&gt;DOCKER_TLS_CERTDIR&lt;/code&gt; 留空，DinD 在 2375 明文听 Docker API。这只在 &lt;code&gt;forgejo_runner&lt;/code&gt; 这张内部网上通，不要把 2375 映射到宿主机端口，更不要映射到 &lt;code&gt;0.0.0.0&lt;/code&gt;。谁能连上 2375，谁就有这套 daemon 的 root。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;privileged: true&lt;/code&gt; 只给 DinD。它要在容器里再跑一套 dockerd、mount、cgroup。job 容器本身 &lt;code&gt;privileged: false&lt;/code&gt;（在 &lt;code&gt;config.yml&lt;/code&gt; 里），workflow 需要 Docker 时走 TCP 调 DinD，不在 job 里再套一层特权 DinD。&lt;/p&gt;&#10;&lt;p&gt;健康检查挡住空窗。runner 的 &lt;code&gt;depends_on&lt;/code&gt; 是 &lt;code&gt;service_healthy&lt;/code&gt; 不是 &lt;code&gt;service_started&lt;/code&gt;：dockerd 还没听 2375 就去 &lt;code&gt;docker pull&lt;/code&gt; job 镜像，失败信息很难看。&lt;code&gt;docker:dind&lt;/code&gt; 的健康检查是 &lt;code&gt;docker info&lt;/code&gt;，重试 12 次、每次 5 秒，足够等首次启动。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;user: &amp;quot;30033:${DOCKER_GID:-999}&amp;quot;&lt;/code&gt;，&lt;code&gt;994&lt;/code&gt; 是这台宿主机的 &lt;code&gt;docker&lt;/code&gt; 组 GID，写在 &lt;code&gt;.env&lt;/code&gt; 的 &lt;code&gt;DOCKER_GID&lt;/code&gt; 里。30033 是 runner 镜像里的非 root 用户（&lt;code&gt;docker exec forgejo-runner id&lt;/code&gt; 输出 &lt;code&gt;uid=30033 gid=994&lt;/code&gt;），宿主机上不存在这个 UID，不用去找它。组 ID 和镜像用户 ID 不是一回事，换机器先 &lt;code&gt;getent group docker&lt;/code&gt; 确认宿主机 GID。&lt;/p&gt;&#10;&lt;p&gt;数据盘属主是 30033。容器内 &lt;code&gt;/data&lt;/code&gt;、&lt;code&gt;.runner&lt;/code&gt;、&lt;code&gt;config.yml&lt;/code&gt; 都是 &lt;code&gt;30033&lt;/code&gt;（或 &lt;code&gt;30033:994&lt;/code&gt;）。compose 命令里那行 &lt;code&gt;chown -R 1000:1000 /data&lt;/code&gt; 是官方模板的残留，实际不起作用——容器以非 root 的 30033 运行，&lt;code&gt;chown&lt;/code&gt; 到别的 uid 会因权限不足失败，&lt;code&gt;2&amp;gt;/dev/null || true&lt;/code&gt; 把 EPERM 吞掉，属主保持 30033 不变。模板还带了一个更值得警惕的东西：宿主机 &lt;code&gt;docker.sock&lt;/code&gt; 的挂载还在 &lt;code&gt;volumes&lt;/code&gt; 里。进程环境变量和 &lt;code&gt;config.yml&lt;/code&gt; 的 &lt;code&gt;docker_host&lt;/code&gt; 都是 &lt;code&gt;tcp://dind:2375&lt;/code&gt;，这份挂载用不上，但它还在，容易让人以为 job 跑在宿主机上。这台目前没出问题，是因为 &lt;code&gt;container.docker_host&lt;/code&gt; 显式指了 DinD；但这条挂载应该删掉，它既没用又误导。&lt;/p&gt;&#10;&lt;p&gt;注册只发生一次。启动脚本看 &lt;code&gt;/data/.runner&lt;/code&gt; 在不在，不在就 &lt;code&gt;forgejo-runner register --no-interactive&lt;/code&gt;，在就直接 &lt;code&gt;daemon&lt;/code&gt;。注册令牌在网站上是&lt;strong&gt;一次性&lt;/strong&gt;的：站点、组织或仓库设置 → Actions → Runners 里生成。注册成功后真正保活的是 &lt;code&gt;.runner&lt;/code&gt; 里那份 token，不是 compose 环境变量。把 &lt;code&gt;.runner&lt;/code&gt; 删了，runner 会当成新机器再注册一次，网站上出现两个 runner，旧的那个要手动下线。&lt;/p&gt;&#10;&lt;p&gt;Label 决定 &lt;code&gt;runs-on&lt;/code&gt;。&lt;code&gt;.env&lt;/code&gt; 里 &lt;code&gt;RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:act-latest&lt;/code&gt;，注册时写进 &lt;code&gt;.runner&lt;/code&gt;——这台机器的 &lt;code&gt;.runner&lt;/code&gt; 里 labels 只有这一条，name=forgejo-runner，address=http://forgejo:3000，id=43。workflow 写 &lt;code&gt;runs-on: ubuntu-latest&lt;/code&gt; 才会被领走。注意 &lt;code&gt;config.yml&lt;/code&gt; 里的 &lt;code&gt;labels: []&lt;/code&gt; 是空的——daemon 阶段用的是 &lt;code&gt;.runner&lt;/code&gt; 里的值，不是 config 里的。镜像名可以按仓改，label 字符串必须对得上。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; 里需要的东西就这些（值自己填）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;FORGEJO_INSTANCE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://forgejo:3000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;RUNNER_REGISTRATION_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;从网站复制&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;RUNNER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;forgejo-runner&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CONTAINER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;forgejo-runner&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;RUNNER_LABELS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu-latest:docker://catthehacker/ubuntu:act-latest&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DOCKER_GID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;994&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h2 id="真正容易写错的是-configyml不是-compose"&gt;真正容易写错的是 config.yml，不是 compose&#10;&lt;/h2&gt;&lt;p&gt;compose 只保证 runner 进程能连上 DinD。job 里面那两个方向，要靠 &lt;code&gt;data/config.yml&lt;/code&gt;。这台文件里改过的值就这几个，其余全是 &lt;code&gt;generate-config&lt;/code&gt; 生成的默认值。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.runner&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;3h&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;insecure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;envs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tcp://dind:2375&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;forgejo-runner&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;host&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;privileged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;docker_host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tcp://dind:2375&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;force_pull&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;code&gt;capacity: 1&lt;/code&gt; 是单机磁盘和内存决定的，不是 runner 的上限。根盘 79G，DinD 的镜像层在 &lt;code&gt;./dind-data&lt;/code&gt;（当前 4.2G），和宿主机镜像层是两份，并发行高了两套 overlay 一起涨。&lt;code&gt;timeout: 3h&lt;/code&gt; 和 Forgejo 实例侧默认 3 小时一致，job 超时按短的那个算。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;insecure: false&lt;/code&gt; 没矛盾。runner 连的是 &lt;code&gt;http://forgejo:3000&lt;/code&gt;，根本不走 TLS；这个开关管的是 HTTPS 证书校验，以后改成走公网域名再决定要不要动它。&lt;/p&gt;&#10;&lt;p&gt;剩下三个值是一套的：&lt;code&gt;runner.envs.DOCKER_HOST&lt;/code&gt; 给 job 注入环境变量，&lt;code&gt;container.docker_host&lt;/code&gt; 告诉 runner 用哪个 daemon 创建 job 容器，&lt;code&gt;container.network&lt;/code&gt; 决定 job 进哪张网。三个必须指向同一个 DinD，改其中一个另外两个不跟着改，就是 checkout 或 &lt;code&gt;docker&lt;/code&gt; 断掉。&lt;/p&gt;&#10;&lt;h2 id="为什么-job-的网络必须是-dind-的-host"&gt;为什么 job 的网络必须是 DinD 的 host&#10;&lt;/h2&gt;&lt;p&gt;先分清两个 Docker。宿主机 dockerd 管 &lt;code&gt;forgejo&lt;/code&gt;、&lt;code&gt;dind&lt;/code&gt;、&lt;code&gt;forgejo-runner&lt;/code&gt;；DinD 里那套 dockerd（server 29.7.2，daemon 名 &lt;code&gt;dind&lt;/code&gt;）管 job。两套 &lt;code&gt;/var/lib/docker&lt;/code&gt;，两套网络命名空间，DinD 默认看不见外面那张叫 &lt;code&gt;forgejo_runner&lt;/code&gt; 的 compose 网。&lt;/p&gt;&#10;&lt;p&gt;job 至少要打通两条路。拿代码：checkout 要连 Forgejo，控制面是 &lt;code&gt;http://forgejo:3000&lt;/code&gt;，这个名字只在外面那张 compose 网上。跑 &lt;code&gt;docker&lt;/code&gt;：客户端要连 daemon，地址 &lt;code&gt;tcp://dind:2375&lt;/code&gt;，这个名字也只在外面那张网上。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;container.network&lt;/code&gt; 留空时，runner 会在 DinD 内部新建一张隔离网桥，把 job 丢进去。那张网桥上的 DNS 不认识 &lt;code&gt;forgejo&lt;/code&gt;，也不认识 &lt;code&gt;dind&lt;/code&gt;，于是看到的是 checkout 超时，或者 &lt;code&gt;Cannot connect to the Docker daemon&lt;/code&gt;。改成 &lt;code&gt;host&lt;/code&gt; 之后，job 加入的是 DinD 容器自己的网络命名空间。DinD 在 &lt;code&gt;forgejo_runner&lt;/code&gt; 网上，hostname 是 &lt;code&gt;dind&lt;/code&gt;，于是 job 里 &lt;code&gt;forgejo&lt;/code&gt; 和 &lt;code&gt;dind&lt;/code&gt; 都能解析。这就是这台机器上 &lt;code&gt;config.yml&lt;/code&gt; 里那行注释的意思，不是&amp;quot;生产环境都应该用 host&amp;quot;。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 实际写在 config.yml 里的注释&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# DinD: job 容器用 dind 的 host 网络，直接共享 dind 容器的网络命名空间，&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 因此能解析 DOCKER_HOST=tcp://dind:2375 并访问 forgejo。&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;host&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;官方安全文档把 &lt;code&gt;container.network: host&lt;/code&gt; 标成危险项，原因很具体：job 和 DinD 同网卡，能打到这张网上所有东西，包括 &lt;code&gt;forgejo_db:5432&lt;/code&gt;。这是用隔离 daemon 换来的，不是零成本。接受它，是因为另外三条路更差。&lt;/p&gt;&#10;&lt;p&gt;宿主机 &lt;code&gt;docker.sock&lt;/code&gt; 直接给 job：&lt;code&gt;container.docker_host: automount&lt;/code&gt; 就行，&lt;code&gt;network&lt;/code&gt; 也可以用 compose 网名，DNS 最省事。但 workflow 等于宿主机 root，单机 Forgejo、Postgres、反代、OIDC 全在这台 79G 盘上，这个口子不留。&lt;/p&gt;&#10;&lt;p&gt;job 走公网 &lt;code&gt;ROOT_URL&lt;/code&gt; clone、Docker 走网桥网关 IP：checkout 可以改成 &lt;code&gt;https://forgejo.lunatic.ren/&lt;/code&gt;，不依赖内部 DNS。但 &lt;code&gt;docker&lt;/code&gt; 还是要有人告诉客户端 DinD 的地址，网桥模式下 &lt;code&gt;dind&lt;/code&gt; 这个名字不存在，只能写死网关 IP 或塞 &lt;code&gt;extra_hosts&lt;/code&gt;。IP 会变，cache 回调 &lt;code&gt;ACTIONS_CACHE_URL&lt;/code&gt; 也要 runner 从 job 里够得着。能做，但等于在给自己养一套私有 DNS。&lt;/p&gt;&#10;&lt;p&gt;让 DinD 共享宿主机网络栈：等于把特权 dockerd 直接铺到宿主机命名空间，比现在这套更难讲清楚边界，没走。&lt;/p&gt;&#10;&lt;p&gt;所以现在这套是三选一之后的结果：daemon 隔离要，内部 DNS 也要，于是 job 借用 DinD 的 netns。&lt;code&gt;cache.host: &amp;quot;forgejo-runner&amp;quot;&lt;/code&gt; 是同一类问题：cache 代理听在 runner 容器里，job 必须能用这个主机名连回来。job 已经跟 DinD 同网，DinD 和 runner 同在 &lt;code&gt;forgejo_runner&lt;/code&gt;，所以这个名字能解析。以后如果把 &lt;code&gt;network&lt;/code&gt; 改回自动建网，cache 会是第二个炸掉的东西，不一定先报 Docker。&lt;/p&gt;&#10;&lt;h2 id="装好之后仍会踩的坑"&gt;装好之后仍会踩的坑&#10;&lt;/h2&gt;&lt;p&gt;compose 里挂着没用上的宿主机 &lt;code&gt;docker.sock&lt;/code&gt;。进程环境变量和 &lt;code&gt;config.yml&lt;/code&gt; 都是 &lt;code&gt;tcp://dind:2375&lt;/code&gt;，这份挂载不参与任何路径，但它还在，容易让人误判 job 跑在哪。只信 &lt;code&gt;container.docker_host&lt;/code&gt;，别信&amp;quot;我挂过 sock&amp;quot;，顺手把这条卷删掉。&lt;/p&gt;&#10;&lt;p&gt;切到 DinD 不会回收旧 job。宿主机上还留着 &lt;code&gt;FORGEJO-ACTIONS-TASK-297&lt;/code&gt; 的 &lt;code&gt;_JOB-build&lt;/code&gt; 容器（&lt;code&gt;node:24-bookworm&lt;/code&gt;，Up 2 days），甚至还在 &lt;code&gt;forgejo_runner&lt;/code&gt; 网上占着一个 IP（172.19.0.5），那是用宿主机 Docker 跑 job 时留下的。DinD 不认识它们，不会删。&lt;code&gt;docker ps&lt;/code&gt; 看到 &lt;code&gt;FORGEJO-ACTIONS-TASK-&lt;/code&gt; 先分清楚它是在宿主机上还是在 &lt;code&gt;docker exec dind docker ps&lt;/code&gt; 里。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;generate-config&lt;/code&gt; 会覆盖你改过的 &lt;code&gt;config.yml&lt;/code&gt;。启动脚本只在没有 &lt;code&gt;.runner&lt;/code&gt; 时重新生成配置，已经注册过就不要删 &lt;code&gt;.runner&lt;/code&gt; 来&amp;quot;重置一下&amp;quot;。要改 label，去网站下线旧 runner、换令牌、再注册。&lt;/p&gt;&#10;&lt;p&gt;workflow 里的 &lt;code&gt;docker&lt;/code&gt; 连哪，看环境变量。runner 注入 &lt;code&gt;DOCKER_HOST=tcp://dind:2375&lt;/code&gt;，job 镜像得带 Docker CLI。&lt;code&gt;catthehacker/ubuntu:act-latest&lt;/code&gt; 这类 CI 镜像一般有，&lt;code&gt;node:20-bookworm&lt;/code&gt; 不一定有。没有 CLI 就装，或者换镜像，别回头去挂 sock。&lt;/p&gt;&#10;&lt;p&gt;2375 没有 TLS。信任边界就是 Docker 网络，谁被加进 &lt;code&gt;forgejo_runner&lt;/code&gt; 谁就能调 DinD。别把监控、临时 debug 容器随手连进来。&lt;/p&gt;&#10;&lt;p&gt;并发和磁盘。&lt;code&gt;capacity: 1&lt;/code&gt;，DinD 数据在 &lt;code&gt;./dind-data&lt;/code&gt;，重启不丢镜像缓存，也会自己膨胀。根盘 79G，CI 镜像和 Forgejo 仓库抢同一块盘，把 DinD 数据盘当无限缓存会先把 Forgejo 写挂。&lt;/p&gt;&#10;&lt;h2 id="从零对照这台机器的顺序"&gt;从零对照这台机器的顺序&#10;&lt;/h2&gt;&lt;p&gt;先建两张外部网 &lt;code&gt;network_bus&lt;/code&gt; 和 &lt;code&gt;forgejo_runner&lt;/code&gt;，前者接你现有的反代，没有反代就先让 Forgejo 只听 &lt;code&gt;127.0.0.1:3000&lt;/code&gt;。然后起 Forgejo 加 Postgres，浏览器能登录，&lt;code&gt;ROOT_URL&lt;/code&gt; 写成真正访问的那个 HTTPS。网站里打开仓库 Actions，出一个 runner 注册令牌。准备 &lt;code&gt;forgejo_runner/data&lt;/code&gt;，属主改成 runner 容器里的 UID（这台是 30033）。填 &lt;code&gt;.env&lt;/code&gt;，起 DinD 加 runner，&lt;code&gt;docker logs dind&lt;/code&gt; 能看到 &lt;code&gt;API listen on [::]:2375&lt;/code&gt;，runner 日志能看到连上 &lt;code&gt;http://forgejo:3000&lt;/code&gt;。网站 Runners 列表出现在线之后，先跑一个只 &lt;code&gt;echo&lt;/code&gt; 的 workflow 确认 checkout 走得通，再跑 &lt;code&gt;docker info&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;docker info&lt;/code&gt; 的 &lt;code&gt;Name&lt;/code&gt; 应该是 &lt;code&gt;dind&lt;/code&gt;，不是宿主机名。是宿主机名的话，sock 又接回去了。这台机器上现在能工作的组合就是 &lt;code&gt;DOCKER_HOST=tcp://dind:2375&lt;/code&gt;、&lt;code&gt;container.docker_host&lt;/code&gt; 同一地址、&lt;code&gt;container.network: host&lt;/code&gt;、runner 用 &lt;code&gt;http://forgejo:3000&lt;/code&gt; 领任务。这四个值是一套，从零搭的时候按这个顺序对，比出问题再猜快得多。&lt;/p&gt;&#10;&lt;p&gt;官方文档对应三份：runner 的 Docker Compose / DinD 安装、job 里怎么用 Docker、&lt;code&gt;container.network&lt;/code&gt; 的安全含义，链接在 Forgejo 官方 Actions 文档里（installation/docker、docker-access、security 三页）。&lt;/p&gt;&#10;&lt;h2 id="收尾"&gt;收尾&#10;&lt;/h2&gt;&lt;p&gt;这套配置把 daemon 隔离和内部 DNS 同时拿到手，代价是 job 与 DinD 共享网络命名空间，网内所有服务对它可见。迁移到别的机器时，四个值一起搬：&lt;code&gt;DOCKER_HOST&lt;/code&gt;、&lt;code&gt;container.docker_host&lt;/code&gt;、&lt;code&gt;container.network: host&lt;/code&gt;、&lt;code&gt;FORGEJO_INSTANCE_URL&lt;/code&gt; 走内网容器名。改动之前先把 cache 路径想清楚，它是这套网络里第二个隐藏依赖。卡住的时候用 &lt;code&gt;docker exec dind docker ps&lt;/code&gt; 和宿主机 &lt;code&gt;docker ps&lt;/code&gt; 对比，能快速判断 job 到底跑在哪套 daemon 上。&lt;/p&gt;&#10;</description></item></channel></rss>