Artifacts 是 workflow 之间传递构建产物的官方通道,自定义 Action 则是把重复逻辑封装成可复用单元的核心手段。本文前半部分深入
actions/upload-artifact与actions/download-artifact的 v4 语法、retention 策略与跨 workflow 传递方案;后半部分完整覆盖 JavaScript / Composite / Docker 三类自定义 Action 的开发、action.yml元数据规范,以及从本地仓库到 Marketplace 的版本发布流程。
一、Artifact 生命周期与基础用法
1.1 什么是 Artifact
Artifact 是 GitHub Actions 提供的文件存储服务,用于在 job 之间传递构建产物(编译结果、测试报告、安装包)。它本质上是绑定在 workflow run 上的临时存储,run 结束后进入 retention 倒计时。
┌────────────┐ upload ┌──────────────┐ download ┌────────────┐
│ Build Job │ ─────────► │ Artifact │ ◄─────────── │ Test Job │
│ (compile) │ │ 存储(仓库级) │ │ (run) │
└────────────┘ └──────────────┘ └────────────┘
1.2 upload-artifact v4 深度语法
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
mkdir -p dist
echo "hello" > dist/app.txt
tar -czf dist.tar.gz dist
- name: Upload single file
uses: actions/upload-artifact@v4
with:
name: app-bundle
path: dist.tar.gz
if-no-files-found: error # error | warn | ignore
compression-level: 6 # 0-9,默认 6
retention-days: 30 # 1-90,默认 90
v4 的关键变化:path 支持多路径与 glob 模式,if-no-files-found 默认 error,compression-level 可调(GZip 0-9,更高的压缩降低存储成本但增加 CPU)。
1.3 download-artifact v4 语法
jobs:
test:
needs: build
runs-on: ubuntu-latest
steps:
# 下载单个 artifact 到指定目录
- uses: actions/download-artifact@v4
with:
name: app-bundle
path: ./artifacts
# v4 支持按 glob 模式批量下载
- uses: actions/download-artifact@v4
with:
pattern: 'coverage-*'
path: ./coverage
merge-multiple: true # 合并到同一目录
一句话:v4 中
download-artifact不再有「不指定 name 就下载全部」的默认行为,必须显式传name、pattern或github-token枚举全部。
二、retention 策略与管理
2.1 保留期模型
| 层级 | 默认保留期 | 说明 |
|---|---|---|
| 仓库设置 | 90 天 | 全局默认,可在 Settings → Actions → General 调整 |
| 工作流级别 | 90 天 | retention-days 覆盖,范围 1-90(企业版可达 400) |
| Enterprise 全局 | 1-400 天 | 组织级策略统一管控 |
2.2 手动与 API 清理
# gh CLI 列出 run 的 artifacts
gh api "/repos/OWNER/REPO/actions/artifacts?per_page=100" \
--jq '.artifacts[] | "\(.name) \(.created_at) \(.expires_at)"'
# 删除指定 artifact
gh api -X DELETE "/repos/OWNER/REPO/actions/artifacts/{artifact_id}"
# 删除过期 artifact 的定时任务示例
gh workflow run cleanup-artifacts.yml
2.3 保留期决策表
| 产物类型 | 建议保留期 | 理由 |
|---|---|---|
| 测试报告/覆盖率 | 7 天 | 足够排查回归,避免堆积 |
| 安装包/二进制 | 90 天 | 关联 Release,可作为备份 |
| 敏感文件(密钥/配置) | 0(不传) | 尽量不入 Artifact,防泄露 |
| 合规审计所需 | 400 天(企业) | 满足内部审计要求 |
三、Artifact 跨 workflow 传递
3.1 问题:Artifact 默认只在 run 内可见
不同 workflow 之间默认无法直接读取彼此的 Artifact。跨 workflow 传递的标准方案是 workflow_run 事件——下游 workflow 在上游 workflow 完成后触发,并通过 API 下载其 Artifact。
3.2 上游:上传 Artifact
# .github/workflows/build.yml
name: Build
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "release" > binary.tar.gz
- uses: actions/upload-artifact@v4
with:
name: release-artifact
path: binary.tar.gz
retention-days: 30
3.3 下游:workflow_run 触发并下载
# .github/workflows/deploy.yml
name: Deploy
on:
workflow_run:
workflows: ["Build"]
types: [completed]
jobs:
deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
actions: read # 需要读取 Artifact
contents: read
steps:
- uses: actions/download-artifact@v4
with:
name: release-artifact
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
path: ./release
- run: ./deploy.sh ./release/binary.tar.gz
| 方案 | 适用场景 | 限制 |
|---|---|---|
同 run 内 needs + Artifact | job 间传递 | 无跨 run 能力 |
workflow_run | workflow 间传递 | 下游无法在上游中传参 |
可复用工作流 workflow_call | 参数化编排 | 需在同一调用树内 |
| GitHub Packages / Release | 长期存储 | 带版本语义,非临时产物 |
四、自定义 Action 的三种类型
4.1 选型对比
| 类型 | 运行方式 | 适用场景 | 依赖 | 推荐度 |
|---|---|---|---|---|
| JavaScript | Node.js 运行时 | 需要处理复杂逻辑、调用 GitHub API | @actions/core、@actions/github | ★★★★★ |
| Composite | 复用 shell/step | 组合现有 steps,无代码逻辑 | 无,仅 YAML | ★★★★★ |
| Docker | 容器内执行 | 语言无关、环境固定 | Dockerfile | ★★★★☆ |
4.2 三种类型的 action.yml 骨架
# JavaScript Action
name: "My JS Action"
description: "在 Node 运行时中执行的 Action"
inputs:
who-to-greet:
description: "要问候的人"
required: true
default: "World"
outputs:
time:
description: "问候时间"
runs:
using: node20
main: dist/index.js
# Composite Action
name: "My Composite Action"
description: "组合多个 steps 的 Action"
inputs:
target:
required: true
outputs:
result:
description: "执行结果"
runs:
using: composite
steps:
- run: echo "准备阶段 ${{ inputs.target }}"
shell: bash
- uses: actions/checkout@v4
- run: echo "完成"
shell: bash
# Docker Action
name: "My Docker Action"
description: "在容器中执行的 Action"
inputs:
command:
required: true
runs:
using: docker
image: Dockerfile
args:
- ${{ inputs.command }}
五、JavaScript Action 开发实战
5.1 项目结构与依赖
JavaScript Action 需要打包成单文件(通常用 @vercel/ncc),因为运行时只会执行 main 指向的入口文件,不会安装 node_modules:
my-action/
├── action.yml
├── package.json
├── src/
│ └── main.js
└── dist/
└── index.js # ncc 打包产物
package.json 关键字段:
{
"name": "my-action",
"main": "dist/index.js",
"scripts": {
"build": "ncc build src/main.js -o dist --source-map"
},
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/github": "^6.0.0"
}
}
5.2 核心 API 用法
const core = require('@actions/core');
const github = require('@actions/github');
try {
// 读取输入
const whoToGreet = core.getInput('who-to-greet');
console.log(`Hello ${whoToGreet}!`);
// 获取触发上下文(repo、sha、event)
const ctx = github.context;
console.log(`repo: ${ctx.repo.owner}/${ctx.repo.repo}`);
// 设置输出
core.setOutput('time', new Date().toTimeString());
// 失败:设置 job 失败并终止
// core.setFailed('遇到错误');
} catch (error) {
core.setFailed(error.message);
}
5.3 与 GitHub API 交互
const core = require('@actions/core');
const github = require('@actions/github');
const token = core.getInput('token', { required: true });
const octokit = github.getOctokit(token);
async function main() {
const { owner, repo } = github.context.repo;
const pr = github.context.payload.pull_request;
if (!pr) return;
// 在 PR 上创建评论
await octokit.rest.issues.createComment({
owner, repo,
issue_number: pr.number,
body: '构建通过 ✅',
});
}
main().catch((e) => core.setFailed(e.message));
一句话:
core.setOutput与core.setFailed是 JavaScript Action 与 workflow 交互的唯二通道;输出可被后续 step 用${{ steps.<id>.outputs.<name> }}引用。
六、Composite 与 Docker Action
6.1 Composite Action 的注意事项
Composite Action 本质是把多个 step 打包,但有几个硬性约束:
runs:
using: composite
steps:
- name: 运行脚本
run: |
echo "${{ github.action_path }}" # 只能在 composite 中使用
./scripts/setup.sh
shell: bash # 每个 run 步骤必须显式声明 shell
- name: 引用外层工作流目录
run: |
# composite 的工作目录是 Action 仓库,不是调用方仓库
echo "使用 inputs 传入调用方路径: ${{ inputs.source-path }}"
shell: bash
| 约束 | 说明 |
|---|---|
shell 必填 | 每个 run step 必须声明 shell |
uses 受限制 | 只能引用其他 action,不能引用 composite action |
env 不继承 | composite 内定义的环境变量不泄漏到外层 |
| 输出需显式 | 用 echo "name=value" >> $GITHUB_OUTPUT 声明 |
| 绝对路径 | 默认 cwd 是 action 仓库,调用方文件需用 ${{ github.workspace }} 或 input 传入 |
6.2 Docker Action 的典型场景
当 Action 需要特定语言/工具链(如 Python 脚本、Golang 编译)时,Docker 是最干净的方式:
# Dockerfile
FROM python:3.12-slim
COPY entrypoint.py /entrypoint.py
ENTRYPOINT ["python", "/entrypoint.py"]
# action.yml
name: "Python Linter"
description: "在固定 Python 环境中运行 lint"
inputs:
lint-path:
description: "要检查的路径"
required: true
runs:
using: docker
image: Dockerfile
args:
- ${{ inputs.lint-path }}
七、发布到 Marketplace 与版本管理
7.1 发布前置条件
- Action 仓库必须是公开仓库
- 必须包含
action.yml(合法元数据) - 仓库需添加
topics(如github-actions、actions)便于被发现 - 打 tag 后即可在 Marketplace 中搜索到
7.2 语义化版本 tag 策略
# 完整版本(不可变)
git tag v1.2.3
# 大版本移动 tag(可变,随补丁推进)
git tag -f v1
git tag -f v1.2
# 推送
git push origin v1.2.3 v1 v1.2 --force
| 用户引用方式 | 风险 | 推荐度 |
|---|---|---|
@v1.2.3 | 完全固定,无漂移 | ✅ 企业级首选 |
@v1 | 随大版本内更新 | ✅ 常用 |
@main | 随仓库最新代码变化 | ❌ 不可复现 |
@<full-sha> | 精确锁定提交 | ✅ 安全加固首选 |
7.3 Release 与 README
每次发版都创建 GitHub Release,并维护 README 的使用示例:
## 用法
```yaml
- uses: your-org/my-action@v1
with:
who-to-greet: "Octocat"
> **一句话**:自定义 Action 的版本承诺是「发布给他人用的契约」——用 `v1.2.3` 固定引用保证可复现,用 `v1` 浮动标签平衡便利,绝不让用户裸引用 `main`。
### 7.4 在 workflow 中测试自定义 Action
发布前应在本地仓库的 CI 中自测,并利用可复用工作流组织级共享:
```yaml
# 调用同仓库的本地 Action(用路径引用,不经市场)
jobs:
test-local-action:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./
with:
who-to-greet: "本地测试"
八、安全与最佳实践
8.1 Artifact 安全
- 敏感文件(
.env、私钥)绝不上传为 Artifact——它可被组织内具actions: read权限者下载 - 设置
retention-days缩短敏感产物生命周期 - fork 的 PR 默认无法访问上游 Secrets,但 Artifact 仍需审慎
8.2 自定义 Action 安全清单
-
action.yml中description非空(Marketplace 强制) - 输入类型明确(
string/boolean/number/choice) - JavaScript Action 打包后校验
dist/与源码一致 - 依赖固定版本或 SHA,避免供应链投毒
- 不将
GITHUB_TOKEN写入日志 - 发布前在真实仓库跑通冒烟测试
总结
Artifacts 与自定义 Action 构成了 GitHub Actions 复用体系的两块基石。
| 维度 | 关键要点 | 常见误区 |
|---|---|---|
| Artifact 传递 | 同 run 用 needs,跨 run 用 workflow_run | 忘记 permissions: actions: read |
| retention 策略 | 默认 90 天,按产物类型缩短 | 敏感文件也设长保留期 |
| Action 类型 | JS 处理逻辑 / Composite 组合 steps / Docker 固定环境 | 把简单 step 组合也写成 JS |
| 版本管理 | v1.2.3 固定 + v1 浮动 | 裸引用 main 导致不可复现 |
| 发布安全 | 固定依赖 SHA、清理 dist、控制 Secrets | 忽略供应链与日志泄露 |
一句话:把「重复的 step 组合」做成 Composite Action,把「需要逻辑的步骤」做成 JavaScript Action,把「需要固定工具链的步骤」做成 Docker Action;再用语义化 tag 与安全加固完成从本地仓库到 Marketplace 的分发闭环。
延伸阅读:
- GitHub Actions Composite Actions — Composite vs Reusable Workflow 深度选型
- GitHub Actions 缓存优化完全指南 — 构建产物与依赖缓存配合
- GitHub Actions 发布自动化 — GitHub Releases 多平台制品上传
- GitHub Actions 可复用工作流 —
workflow_call参数化编排
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。