act 怎么通过 --env-file 加载本地环境:.env 与 YAML 文件的识别规则和参数优先级
用 act 在本地运行 GitHub Actions 工作流时,常需要把本地环境里的变量(数据库地址、API 密钥所在的中间变量等)注入到 job 容器中。act 提供了 --env-file 参数完成这件事:它把指定文件读成键值对,作为容器内的环境变量使用。这个参数的默认值是 .env,文件既可以是 dotenv 格式(如 .env),也可以是 YAML(扩展名为 .yml/.yaml)。本文说明它的文件识别规则、路径解析方式,以及当同一变量在 --env 参数、.actrc 和 env-file 中同时出现时谁生效,并给出验证加载结果的方法。
--env-file 的定义与路径解析
在 cmd/root.go 中,该参数被注册为全局持久参数:
rootCmd.PersistentFlags().StringVarP(&input.envfile, "env-file", "", ".env",
"environment file to read and use as env in the containers")
即默认读取工作目录下的 .env,读取结果"作为容器内的环境变量使用"。不显式传参时就是走 .env 这个默认路径。
路径解析逻辑在 cmd/input.go 的 Input.resolve 中:相对路径会拼接到 --directory(短选项 -C)指定的工作目录(默认 .)之后,绝对路径则原样使用。也就是说:
# 在仓库根目录下运行 push 事件的工作流,env 文件取默认的 ./.env
act push
# 显式指定 env 文件;相对路径相对于当前目录(-C 未指定时)
act push --env-file env/local.yml
# 用 -C 把工作目录切到别的目录,--env-file 的相对路径也相对该目录解析
act -C /home/user/myrepo push --env-file .env
运行入口在 cmd/root.go 的 newRunCommand 中,加载顺序是:
log.Debugf("Loading environment from %s", input.Envfile())
envs := parseEnvs(input.envs) // 先放入命令行 --env 的键值对
_ = readEnvs(input.Envfile(), envs) // 再读 env-file 文件
.env 与 YAML 文件的识别规则
格式识别只看文件扩展名。readEnvsEx(cmd/root.go)先检查文件是否存在,然后:
- 扩展名为
.yml或.yaml:按 YAML 解析成map[string]string,即键: 值的映射; - 其他扩展名(包括无扩展名、
.env等):交给godotenv.Read解析,即KEY=value每行一个键值对。
dotenv 风格文件的最小例子(与仓库测试数据 pkg/container/testdata/valid.env 相同):
ENV1=value1
YAML 文件支持多行值。仓库的 cmd/testdata/secrets.yml 就是这样一份测试数据,cmd/root_test.go 中的 TestReadEnv 验证了它被读成 mysecret 键、值为三行 line1/line2/line3(块量级 | 保留换行):
mysecret: |
line1
line2
line3
两个边界行为要注意:
- 文件不存在时不报错。
readEnvsEx对os.Stat失败静默返回false,act 继续运行,只是没有从该文件注入任何变量。拼错--env-file路径时不会出现任何提示,这是排查"变量没生效"时首先要确认的点。 - 文件存在但解析失败时会直接终止。代码路径是
log.Fatalf("Error loading from %s: %v", path, err),即 YAML 写错缩进或格式非法时 act 会报错退出,错误信息包含出问题的文件路径。
另外,env-file 的键名按原样使用,不会做大小写转换:readEnvs 调用的是 readEnvsEx(path, envs, false)(cmd/root.go),caseInsensitive 为 false。测试数据中的小写键 mysecret 就以小写键名被写入环境(见 cmd/root_test.go)。而 --secret-file 走的是大小写不敏感分支,两者不要混为一谈。
参数优先级:同一变量多处定义时谁生效
优先级规则同样来自 cmd/root.go 的代码。
--env 参数优先于 env-file。 newRunCommand 先用 parseEnvs(input.envs) 把命令行 --env KEY=value 的键值对放进 envs,随后 readEnvsEx 写入文件内容时有显式判断:
if _, ok := envs[k]; !ok {
envs[k] = v
}
也就是说,env-file 只会补齐 --env 里不存在的键;同名键以 --env 的值为准。parseEnvs 按第一个 = 拆分,--env FOO=prefix/foo/suffix 这种带 = 的值是安全的(只拆一次)。仓库测试数据 cmd/testdata/env.actrc 中就有一条 --env FOO=prefix/${FOO}/suffix,测试 cmd/root_test.go 确认它最终生成了 --env FOO=prefix/foo/suffix。
--env-file 参数本身的取值,命令行优先于 .actrc。 act 支持 .actrc 配置文件,查找顺序(见 cmd/root.go 的 configLocations 注释与实现):
- XDG 规范路径(
xdg.ConfigFile("act/actrc")); - 主目录下的
~/.actrc; - 当前调用目录下的
./.actrc。
args() 把三个文件中读到的参数按上述顺序依次追加,最后再追加 os.Args[1:](命令行实参):
for _, f := range actrc {
args = append(args, readArgsFile(f, true)...)
}
args = append(args, os.Args[1:]...)
同一参数名出现多次时,后面的值覆盖前面的,因此最终优先级为:命令行 > ./.actrc > ~/.actrc > XDG 配置路径。.actrc 每行写一个选项(可带参数),并且支持 ${VAR} 形式的环境变量展开,例如 cmd/testdata/env.actrc 的内容:
--artifact-server-path $FAKEPWD/.artifacts
--env FOO=prefix/${FOO}/suffix
测试 cmd/root_test.go 的 TestReadArgsFile 表明:当宿主环境中设置了 FAKEPWD 和 FOO 时,上面两行会被展开为 --artifact-server-path /fake/test/pwd/.artifacts 与 --env FOO=prefix/foo/suffix。所以你也可以把 --env-file env/local.yml 写进 .actrc,省去每次敲参数。
验证加载是否生效
看调试日志。 加 -v(verbose,把日志级别调到 Debug)后,newRunCommand 会打印加载的文件路径:
act -v push --env-file env/local.yml
输出中的 Loading environment from <路径> 一行会给出解析后的完整路径,可以先确认文件是否指向你以为的那个文件。
用 --dryrun 校验组合是否正确。 --dryrun(短选项 -n)的说明是 "disable container creation, validates only workflow correctness",即不创建容器、只校验工作流正确性。在不打算真正跑容器时,可以用它配合 --env-file 快速确认参数组合没有报错(注意 --dryrun 下不会真正在容器里验证变量值)。
用测试用例作为参照。 pkg/container/docker_cli_test.go 的 TestParseEnvfileVariables 展示了预期行为(文档示例,非运行时固定输出):
--env-file=testdata/valid.env解析出的容器环境为ENV1=value1;- 同时给出
--env=ENV2=value2时,容器环境包含ENV1=value1和ENV2=value2两项; - 文件不存在时返回
open nonexistent: no such file or directory(该用例针对 docker 参数层,与前述"act 层文件缺失静默跳过"是两个不同层次,排查时注意区分报错来自哪一层)。
工作流内部的步骤读取的是注入到容器中的环境变量(参数说明原文为 "use as env in the containers"),变量是否可用以步骤内的实际行为为准。
限制与注意点
- 文件不存在是静默行为:
--env-file指到不存在的路径时 act 不会报错,只是没有注入任何变量。变量"没生效"时,先用-v看Loading environment from的路径是否是你预期的文件。 - 文件存在但内容非法(如 YAML 缩进错误)会直接终止 act,错误信息为
Error loading from <路径>: <原因>。 - 识别规则完全由扩展名决定:内容写成 dotenv 但扩展名是
.yaml会被按 YAML 解析而失败;反之.env扩展名的 YAML 内容也走 dotenv 解析。想使用 YAML 的多行值等能力,文件必须命名为.yml或.yaml。 - env-file 的键区分大小写且不自动大写;需要大小写不敏感读取的是
--secret-file路径,不要靠改 env-file 的键名大小写去对齐 secret。 --env-file注入的是容器环境变量,不会改变宿主 shell 的环境;.actrc里的${VAR}展开读取的是运行 act 的宿主环境。
排查与扩展的入口文件:cmd/root.go(参数定义与加载顺序)、cmd/input.go(路径解析)、cmd/root_test.go(actrc 与 YAML 读取的测试用例)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00