Learn
GitHub Actions/11-env-secrets

环境变量与 Secrets

CI/CD 里大量配置并不适合写死在代码里:服务器地址、版本号前缀可以公开,但 API Token、密码、私钥绝对不能进代码仓库。GitHub Actions 把这两类东西分开处理——公开配置用 env,机密用 secrets。本章讲清楚它们的写法、作用范围,以及那条最重要的安全铁律:secret 的值绝不能以任何形式进日志。

1. env 的三个层级:小范围覆盖大范围

env 可以在三个层级定义,作用范围从大到小是:workflow 级 → job 级 → step 级。层级越小的定义会覆盖层级大的同名变量。

name: Env Demo
 
env:                         # workflow 级:所有 job、所有 step 都能用
  LEVEL: workflow
  REGION: ap-shanghai
 
on: [push]
 
jobs:
  build:
    env:                     # job 级:只在本 job 内有效,覆盖 workflow 级
      LEVEL: job
    runs-on: ubuntu-latest
    steps:
      - name: 打印 job 级变量
        run: echo "$LEVEL / $REGION"   # 输出:job / ap-shanghai
 
      - name: step 级变量
        env:                   # step 级:只在本 step 内有效,覆盖 job 级
          LEVEL: step
        run: echo "$LEVEL"     # 输出:step

规则总结:

  • 就近优先:step 级覆盖 job 级,job 级覆盖 workflow 级。
  • 同级不串门:job A 的 env 不会自动出现在 job B 里;想跨 job 共享,要用第 13 章讲的 job output / artifact。
  • 在 run 脚本里用 shell 语法 $VAR 读取;在 uses 等表达式中用 ${{ env.VAR }} 引用。
ℹ️`$VAR` 与 `${{ env.VAR }}` 的区别
  • 在 run: 后面写的是 shell 脚本,用 $VAR 或 ${VAR} 读取环境变量(由 runner 的 shell 注入)。
  • 在 YAML 的表达式上下文(比如 if:、step 的 with: 参数里)要用 ${{ env.VAR }},这是在 YAML 被解析、交给 Actions 引擎求值前就展开的。
  • 混用会出错:在 run 里用 ${{ env.VAR }} 也能工作(因为先展开成字符串再交给 shell),但在 if: 里用裸 $VAR 是无效的。

2. 在 run 与 uses 中引用 env

${{ env.FOO }} 在表达式上下文里处处可用。下面两个例子分别展示在 run 和 uses 中引用:

jobs:
  demo:
    env:
      DEPLOY_ENV: staging
    runs-on: ubuntu-latest
    steps:
      - name: 在 run 中引用
        run: echo "当前部署环境是 ${{ env.DEPLOY_ENV }}"
 
      - name: 在 uses 的参数中引用
        uses: some/action@v1
        with:
          environment: ${{ env.DEPLOY_ENV }}

注意上面 run 那行:这里用 ${{ env.DEPLOY_ENV }} 和用 $DEPLOY_ENV 效果相同,因为表达式先被展开成字符串 staging 再交给 shell。两种写法都合法,团队里统一一种即可。

3. Secrets:在仓库设置里配置,用 ${{ secrets.TOKEN }} 引用

Secret(机密)和 env 最大的区别:它的值不写进仓库文件,而是存放在 GitHub 的加密存储里,在 workflow 运行时才被注入。

配置位置有三个层级:

位置入口作用范围
仓库级仓库 Settings > Secrets and variables > Actions > Repository secrets本仓库所有 workflow
环境级(Environment)Settings > Environments 里给某个环境配 secret仅在部署到该环境(environment)时可用,可设保护规则
组织级组织 Settings > Secrets组织下多个仓库共享

引用方式永远统一:

steps:
  - name: 调用外部 API
    env:
      TOKEN: ${{ secrets.API_TOKEN }}
    run: curl -H "Authorization: Bearer $TOKEN" https://api.example.com/deploy
⚠️secret 的价值在于不落地

secret 之所以安全,是因为它不经过你的文件、不经过 Git 历史。一旦你把 secret 写进 .yml 文件、写进代码、或者 echo 出来,它就不再是 secret 了。配置时只配名字和值,引用时只用 ${{ secrets.XXX }},切勿在文件里出现真实值。

4. 日志自动打码(MASK)与那条铁律

GitHub 会自动把所有 secret 的值在日志输出中打码(mask):即使某个命令不小心把 secret 的值打印出来了,日志里也只会显示 ***。这是 GitHub 的安全兜底。

但是——打码是"尽力而为"的,它挡不住你的主动泄露。下面这些行为会绕过打码:

# ❌ 危险:把 secret 原样 echo 到日志
echo "$TOKEN"
 
# ❌ 危险:写进文件再 cat / 上传,打码只对"日志流"生效,文件内容不在保护范围内
echo "$TOKEN" > token.txt
cat token.txt
 
# ❌ 危险:把 secret 当 artifact 上传,artifact 不在日志打码保护范围内
⚠️三条绝对不要做的事
  1. 不要 echo ${{ secrets.X }} 到日志——即便你以为有用,也会把值暴露(而且打码只在"GitHub 识别出这是 secret 值"时生效,拼装、转义、部分打印都可能露出破绽)。
  2. 不要把 secret 写入文件后再 cat / 打印——打码只保护日志流,文件内容不在保护范围内。
  3. 不要把含 secret 的文件作为 artifact 上传——artifact 可下载,等于把机密打包发出去。

记住:GitHub 的打码是安全网,不是你可以随意 echo 的理由。

5. GITHUB_ENV:把变量传给后续 step

有时候某个 step 计算出了一个值(比如版本号、构建 ID),想让同一个 job 里后面的 step也能用。办法是往一个特殊文件 $GITHUB_ENV 里追加 NAME=VALUE:

steps:
  - name: 计算版本号
    run: |
      VERSION="v1.2.$(date +%Y%m%d)"
      echo "VERSION=$VERSION" >> $GITHUB_ENV
 
  - name: 后续 step 使用
    run: echo "本次构建版本是 $VERSION"

原理:GitHub 在 runner 上维护一个 $GITHUB_ENV 文件,你往里追加的每一行 NAME=VALUE 会被注入到当前 job 后续 step 的环境变量中。注意:

  • 只对"后面的 step"生效,当前 step 这一行之后也不能立即用,要等下一个 step。
  • 适合传递简单的字符串;多行内容可用 >> $GITHUB_ENV 配合 <<EOF 语法(进阶,用到再查)。

6. GITHUB_OUTPUT:把值传给后续 step 或作为 job output

与 GITHUB_ENV 类似,还有一个特殊文件 $GITHUB_OUTPUT,用来把值输出出去。它的两个用途:

  1. 传给同 job 后续 step(通过 steps.<id>.outputs.<name> 读取)。
  2. 作为 job 级别的 output,供依赖它的其他 job 使用(呼应第 13 章"job 之间传数据")。
steps:
  - name: 生成构建产物名
    id: meta
    run: echo "artifact_name=build-$(date +%s)" >> $GITHUB_OUTPUT
 
  - name: 读取并使用
    run: echo "产物名叫 ${{ steps.meta.outputs.artifact_name }}"

这里 id: meta 给 step 起了名字,后续用 ${{ steps.meta.outputs.artifact_name }} 读取。GITHUB_OUTPUT 与 GITHUB_ENV 的区别在于:前者语义上是"这个 step 产出了一个结果",更适合配合 needs 跨 job 传递;后者语义上是"设置一个环境变量"。第 13 章会详细展开 job 之间的数据传递。

💡`env` 与 `secrets` 的本质区别
  • env(公开配置变量):值可以安全出现在日志、配置里,比如 NODE_ENV=production、REGION=ap-shanghai。随便打印没问题。
  • secrets(机密):值一旦泄露就有真实风险,比如数据库密码、云厂商 AK/SK、部署 token。绝不打印、绝不进文件、绝不传 artifact。
  • 一个简单判断:如果这个值出现在日志里你会紧张,那它就该是 secret,而不是 env。

7. 完整示例:设置 env、用 secrets 调外部 API、用 GITHUB_ENV 传变量

name: Deploy
 
on:
  push:
    branches: [main]
 
env:
  DEPLOY_ENV: production
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production        # 可选:绑定受保护环境,使用环境级 secret
    steps:
      - uses: actions/checkout@v4
 
      - name: 计算本次发布标签
        run: |
          TAG="release-$(date +%Y%m%d-%H%M%S)"
          echo "TAG=$TAG" >> $GITHUB_ENV
 
      - name: 调用部署 API(使用 secret)
        env:
          API_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          echo "开始部署到 ${{ env.DEPLOY_ENV }},标签 ${{ env.TAG }}"
          curl -X POST https://api.example.com/deploy \
            -H "Authorization: Bearer $API_TOKEN" \
            -d "{\"tag\":\"${{ env.TAG }}\"}"

注意:第 25 行的 echo 打印的是 ${{ env.TAG }} 和 ${{ env.DEPLOY_ENV }},都是公开变量,安全;真正的机密 API_TOKEN 只作为请求头发给 API,没有 echo 出来。

8. 小结

  • env 有三个层级:workflow / job / step,小范围覆盖大范围;run 中用 $VAR,表达式上下文用 ${{ env.FOO }}。
  • Secrets 配置在仓库 / 环境 / 组织三级设置里,引用统一为 ${{ secrets.TOKEN }},值不进仓库文件。
  • GitHub 会对 secret 值在日志中自动打码(MASK),但这只是兜底,不能主动 echo、不能写文件 cat、不能当 artifact 上传。
  • $GITHUB_ENV 把变量追加进去,供同 job 后续 step 使用。
  • $GITHUB_OUTPUT 把值输出,供同 job 后续 step(steps.<id>.outputs.<name>)或跨 job(job output)使用,第 13 章详述。
  • 公开配置用 env,真实机密用 secrets;判断标准:出现在日志里你会紧张吗?
  • 下一章:条件与表达式 → 我们将学习用 if: 配合 github / needs 等上下文和 success() / failure() 等函数,精确控制 job 与 step 是否执行。
🎯练习
  1. 下面这段 YAML 中,echo 实际会输出什么?为什么?
env:
  NAME: global
jobs:
  a:
    env:
      NAME: joblevel
    runs-on: ubuntu-latest
    steps:
      - name: s1
        env:
          NAME: steplevel
        run: echo "$NAME"
      - name: s2
        run: echo "$NAME"
  1. 你想在 workflow 里调用一个需要 AWS_SECRET_ACCESS_KEY 的外部 CLI。请写出正确引用 secret 的 env 配置,并说明为什么不能写 echo "$AWS_SECRET_ACCESS_KEY" 调试。
  2. step A 算出了一个提交哈希 SHA=abc123,想让 step B(同 job)打印它。请用 $GITHUB_ENV 写出 step A 的代码,以及 step B 读取并打印的代码。
  3. 判断:把数据库密码放在 env 而不是 secrets 里,只要不在日志里 echo 就安全。这个说法对吗?为什么?