环境变量(env)的三个层级
层级优先级:Step > Job > Workflow
name: 环境变量示例
env: # Workflow 级别(最低优先级)
APP_NAME: "my-app"
NODE_ENV: "production"
jobs:
build:
runs-on: ubuntu-latest
env: # Job 级别(覆盖 Workflow 级别)
BUILD_MODE: "release"
NODE_ENV: "staging" # 覆盖了 workflow 级别的 production
steps:
- name: 打印变量
env: # Step 级别(最高优先级)
NODE_ENV: "test" # 此步骤内覆盖 staging
run: |
echo "APP_NAME: $APP_NAME" # my-app
echo "BUILD_MODE: $BUILD_MODE" # release
echo "NODE_ENV: $NODE_ENV" # test(step级覆盖)
- name: 动态设置环境变量
run: |
# 将变量写入 GITHUB_ENV,后续步骤可使用
echo "BUILD_TIME=$(date -u +%Y%m%d%H%M%S)" >> $GITHUB_ENV
echo "GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: 使用动态变量
run: echo "构建时间: $BUILD_TIME, Commit: $GIT_HASH"
Secrets 安全管理
配置 Secrets
在 GitHub 仓库中设置 Secrets:Settings → Secrets and variables → Actions → New repository secret。Secrets 的值一旦保存,任何人(包括仓库管理员)都无法再次查看,只能使用或删除。
Secrets 的三个层级
Repository Secrets
仓库级别的 Secrets,只有该仓库的 Workflow 可以访问。适合单个项目专用的 API 密钥、部署凭证。
Environment Secrets
环境级别的 Secrets(需要在 job 中声明 environment),可以为 staging 和 production 环境配置不同的值,并设置审批保护规则。
Organization Secrets
组织级别的 Secrets,可以在组织内的多个仓库中共享。适合公司公共服务的凭证(如通知服务、监控平台)。
steps:
# 在 env 中注入 Secret(推荐方式)
- name: 部署到 AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: aws s3 sync ./dist s3://my-bucket
# 直接作为参数(某些 Action 要求)
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# GITHUB_TOKEN:自动生成,无需手动创建
- name: 创建 Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Secrets 安全规则
- 永远不要在
echo中直接打印 Secret(GitHub 会自动屏蔽,但仍是不良实践) - Fork 的 PR 默认无法访问父仓库的 Secrets(安全设计,防止恶意 PR 泄露密钥)
- Secrets 名称不能以
GITHUB_开头(保留前缀) - 定期轮换 Secrets,尤其是高权限凭证
上下文对象(Context)
最常用的上下文
github 上下文
关于 Workflow 运行和触发事件的信息。包含:
github.sha(当前提交哈希)、github.ref_name(分支/标签名)、github.actor(触发者)、github.event_name(事件类型)、github.repository(仓库全名)。runner 上下文
Runner 环境信息。
runner.os(Linux/Windows/macOS)、runner.arch(X64/ARM64)、runner.temp(临时目录路径)。steps 上下文
访问同 Job 中其他步骤的输出和状态。
steps.STEP_ID.outputs.KEY(步骤输出)、steps.STEP_ID.outcome(success/failure/skipped/cancelled)。needs 上下文
访问依赖 Job 的输出。
needs.JOB_ID.outputs.KEY。vars 上下文
访问仓库/环境/组织的变量(非敏感配置值,可在 UI 中查看)。
vars.APP_NAME。表达式语法 ${{ }}
条件与函数
steps:
# 条件函数
- name: 只在 main 分支运行
if: github.ref == 'refs/heads/main'
run: echo "这是主分支"
- name: 失败时通知
if: failure()
run: echo "流水线失败了!"
- name: 总是执行(包括失败后)
if: always()
run: echo "清理临时文件"
# 字符串函数
- name: 使用字符串函数
run: |
# contains():检查是否包含子串
if [[ "${{ github.event.head_commit.message }}" == *"[skip ci]"* ]]; then
echo "跳过 CI"
fi
# 三元表达式(利用 if 语法模拟)
- name: 设置部署环境
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
DEPLOY_ENV="production"
else
DEPLOY_ENV="staging"
fi
echo "DEPLOY_ENV=$DEPLOY_ENV" >> $GITHUB_ENV
# toJSON():调试输出上下文
- name: 输出 github 上下文(调试用)
run: echo '${{ toJSON(github) }}'
内置函数速查
| 函数 | 用途 | 示例 |
|---|---|---|
| contains(str, substr) | 检查包含关系 | contains(github.ref, 'release') |
| startsWith(str, prefix) | 检查前缀 | startsWith(github.ref, 'refs/tags/') |
| endsWith(str, suffix) | 检查后缀 | endsWith(matrix.os, 'latest') |
| format(str, ...) | 字符串格式化 | format('v{0}.{1}', major, minor) |
| join(arr, sep) | 数组连接 | join(matrix.os, ',') |
| toJSON(value) | 转 JSON 字符串 | toJSON(github.event) |
| fromJSON(str) | 解析 JSON | fromJSON(steps.data.outputs.json) |
| hashFiles(pattern) | 文件内容哈希 | hashFiles('**/package-lock.json') |
| success() | 之前步骤都成功 | if: success() |
| failure() | 之前有步骤失败 | if: failure() |
| always() | 总是执行 | if: always() |
| cancelled() | 工作流被取消 | if: cancelled() |
Jobs 之间传递输出(outputs)
Job 的 outputs 允许一个 Job 将计算结果传递给后续依赖 Job,是实现动态流水线的关键机制:
jobs:
# 第一个 Job:确定版本号
determine-version:
runs-on: ubuntu-latest
outputs:
# 声明该 Job 的输出
version: ${{ steps.semver.outputs.version }}
is-release: ${{ steps.check-tag.outputs.is-release }}
steps:
- uses: actions/checkout@v4
- name: 从 tag 提取版本号
id: check-tag
run: |
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
echo "is-release=true" >> $GITHUB_OUTPUT
echo "tag-version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
else
echo "is-release=false" >> $GITHUB_OUTPUT
fi
- name: 计算语义化版本
id: semver
run: |
VERSION="${{ steps.check-tag.outputs.tag-version || '0.0.0-dev' }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Computed version: $VERSION"
# 第二个 Job:使用第一个 Job 的输出
build:
needs: determine-version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 注入版本信息
run: |
VERSION="${{ needs.determine-version.outputs.version }}"
echo "Building version: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV
- run: npm run build
# 第三个 Job:条件触发(只在 Release 时运行)
publish:
needs: [determine-version, build]
# 只有当 is-release 为 true 时才运行
if: needs.determine-version.outputs.is-release == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Publishing v${{ needs.determine-version.outputs.version }}"
Environment(部署环境)
Environment 是 GitHub Actions 的部署环境保护机制,可以为生产环境配置手动审批和等待规则:
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment:
name: staging # 环境名称
url: https://staging.myapp.com # 部署后显示的链接
steps:
- run: echo "部署到 staging 环境"
env:
# Environment Secrets 只有在声明了对应 environment 时才可访问
API_KEY: ${{ secrets.API_KEY }} # staging 环境的 Secret
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production # 此环境配置了"必须审批"规则
url: https://myapp.com
steps:
- run: echo "部署到 production 环境"
env:
API_KEY: ${{ secrets.API_KEY }} # production 环境的 Secret(不同的值)
Environment 保护规则配置
在 Settings → Environments 中为 production 环境配置:Required reviewers(必须由指定成员审批后才能部署)、Wait timer(部署前等待 N 分钟)、Deployment branches(只有特定分支可以部署到此环境)。这为生产部署提供了人工审核的保障。
本章小结
本章核心要点
- env 三级覆盖:Step > Job > Workflow,同名变量取最内层的值;用 GITHUB_ENV 文件在步骤间动态传递变量,用 GITHUB_OUTPUT 在步骤间传递输出值。
- Secrets 安全设计:Fork PR 默认无法访问 Secrets(防止恶意 PR 泄露密钥);Environment Secrets 与 Repository Secrets 分离,staging/production 使用不同值;GITHUB_TOKEN 是自动生成的临时令牌。
- Context 对象:github(触发信息)、runner(运行环境)、steps(步骤输出)、needs(依赖 Job 输出)是最常用的四个上下文;toJSON(context) 是调试时查看完整上下文的好方法。
- 表达式函数:success/failure/always/cancelled 控制条件步骤执行;contains/startsWith/hashFiles 用于字符串和文件操作;fromJSON/toJSON 用于结构化数据传递。
- Job outputs:通过 $GITHUB_OUTPUT + jobs.XXX.outputs 实现 Job 间数据传递;配合 if: needs.xxx.outputs.key 可以实现基于计算结果的条件触发。