<!-- title: 构建Dockerfile智能审计Skill -->

# 用 Skills 把 Dockerfile 审计沉淀成一键能力：从目录结构到 eval 闭环

## 概述

本教程面向已经摸过 Claude Code、想把自己的工程经验固化成可复用能力的开发者。我们将以"从零构建一个 Dockerfile 智能审计 Skill"为贯穿案例，完整走通 Skills 开发的全流程：理解三层渐进式披露架构、编写符合规范的 `SKILL.md` frontmatter、用动态上下文注入把实时文件内容喂给模型、用 `references/` 沉淀知识库、用 `scripts/` 封装确定性脚本、最后用 `skill-creator` 跑盲测 eval 量化效果。读完即可独立交付一个生产可用的 Skill。

## 前置条件

- Claude Code v2.1.145 及以上（本教程用到的 `/run` 系列内建 Skill 与部分 frontmatter 字段依赖该版本）
- 一个装有 Dockerfile 的测试仓库（没有也行，文中会给最小样例）
- 本机装有 `python3`（Skill 内置审计脚本用纯标准库，无需额外依赖）
- 基础的 YAML 与 Markdown 语法知识

---

## 第一步：先吃透 Skills 的三层渐进式披露

动手之前必须想清楚一件事：**Skill 的本质不是"一段提示词"，而是"一本可按需翻阅的入职手册"**。Anthropic 在工程博客里把它的加载机制描述为三个层级，这是整个设计的灵魂，写不好 Skill 几乎都是因为没理解它。

```mermaid
flowchart TB
    A["启动会话"] --> B["第1层: 元数据<br/>name + description<br/>~100 词, 始终驻留"]
    B --> C{"Claude 判断<br/>是否相关?"}
    C -- 是 --> D["第2层: SKILL.md 正文<br/>触发时加载<br/>建议 1500-2000 词"]
    C -- 否 --> E["不加载, 零成本"]
    D --> F{"遇到细分场景?"}
    F -- 是 --> G["第3层: 捆绑资源<br/>references/ scripts/ assets/<br/>按需加载, 容量近乎无限"]
    F -- 否 --> H["直接执行任务"]
    G --> H
```

三个层级对应三种成本：

| 层级 | 内容 | 加载时机 | 体积建议 |
|------|------|----------|----------|
| 第 1 层 | `name` + `description` | 会话启动即驻留系统提示词 | 控制在约 100 词，且与 `when_to_use` 合计不超过 1536 字符 |
| 第 2 层 | `SKILL.md` 正文 | 模型判定相关、或用户手动 `/skill-name` 触发时 | 1500–2000 词为佳，硬上限 500 行 |
| 第 3 层 | `references/`、`scripts/`、`assets/` | 模型按需读取，脚本可不进上下文直接执行 | 单文件可 2000–5000+ 词，总量无上限 |

**预期结果**：理解这一点后你会明白——把所有规则塞进 `SKILL.md` 是反模式。`SKILL.md` 是目录和导航，不是百科全书。

**常见问题**：
- *为什么我写的 50 行规则模型经常不遵守？* 多半是 `description` 没写好，模型压根没触发第 2 层，规则从未被加载。先解决触发，再谈内容。
- *Skill 之间会互相挤压吗？* 会。所有 Skill 的 `description` 共享一个"描述预算"，默认是模型上下文窗口的 1%。装得太多时，用得少的会被截断甚至只剩名字。跑 `/doctor` 可以看到哪些被截断了。

## 第二步：用"具体用例"反推 Skill 内容

官方 `skill-development` 模板强调，写 Skill 之前先收集 3–5 个真实用例，再反推需要哪些资源。我们围绕 Dockerfile 审计列举用例并分析：

| 真实用例 | 反推出的可复用资源 |
|----------|-------------------|
| "帮我审计这个 Dockerfile" | 需要一个确定性静态分析脚本 `scripts/audit_dockerfile.py` |
| "镜像太大怎么瘦身" | 需要最佳实践知识库 `references/best-practices.md` |
| "检查有没有用 root 跑" | 脚本里内置 USER/根用户检测规则 |
| "这个 Dockerfile 安全吗" | 需要 secrets、基础镜像漏洞的检测 + 知识库章节 |
| "按公司规范生成修复版" | 需要一份模板资产，但本期先聚焦审计，模板留作迭代 |

**预期结果**：得到一个清晰的资源清单——1 个脚本 + 1 个知识库文件 + 1 个精简的 `SKILL.md`。这决定了第三步的目录结构。

**常见问题**：*不知道该写多少规则进知识库？* 别预先穷举。先让 Skill 在真实 Dockerfile 上跑几轮，把模型反复"重新发现"的规则沉淀进 `references/`——这是"迭代沉淀"而非"预先设计"。

## 第三步：创建目录骨架

Skill 的命令名由它所在的目录决定（frontmatter 的 `name` 只影响列表里的显示名，不影响你敲的命令）。我们把它放在个人级目录，所有项目都能用：

```bash
mkdir -p ~/.claude/skills/container-lint/{references,scripts}
```

最终结构：

```text
container-lint/
├── SKILL.md                      # 入口，必需
├── references/
│   └── best-practices.md         # 详尽规则，按需加载
└── scripts/
    └── audit_dockerfile.py       # 确定性静态分析脚本
```

**预期结果**：目录建好后，Claude Code 的"实时变更检测"会立即发现它——在已运行的会话里新建/修改 `~/.claude/skills/` 下的 Skill 无需重启即可生效（仅对 `SKILL.md` 文本生效；若该目录同时是 plugin，hooks/MCP 变更需 `/reload-plugins`）。

**常见问题**：*该放个人级还是项目级？* 个人级（`~/.claude/skills/`）跨项目可用；项目级（`.claude/skills/`）随仓库走、可提交版本控制、团队共享。本例是通用工具，放个人级；如果是公司专有镜像规范，放项目级并提交 Git。

## 第四步：写 frontmatter——决定 Skill 何时被触发

frontmatter 是第 1 层，写不好后面全白搭。三个铁律：

1. **第三人称 + 具体触发短语**：不要写"Use this skill when…"（第二人称），要写"This skill should be used when the user asks to '审计 Dockerfile', '优化镜像'…"，把用户真实会说的原话塞进去。
2. **核心用例放最前**：`description` + `when_to_use` 合计在 Skill 列表里被截断到 1536 字符，关键词靠前才不会被切掉。
3. **控制谁能触发**：审计是只读分析，可以让模型自动触发；若是有副作用的动作（如部署），加 `disable-model-invocation: true` 只允许手动触发。

```yaml
---
name: container-lint
description: 审计 Dockerfile 与容器镜像构建配置，定位层冗余、缺失多阶段构建、root 运行、硬编码密钥、过大基础镜像等问题并给出可执行优化建议。当用户提到"审计 Dockerfile"、"优化镜像构建"、"镜像瘦身"、"检查容器最佳实践"、"分析 Dockerfile 安全"、"减小镜像体积"时触发。
when_to_use: 用于任何涉及 Dockerfile、Containerfile、镜像构建、CI 构建优化的请求。
allowed-tools: Read Glob Bash(python3 *)
argument-hint: [Dockerfile路径，留空则审计当前目录]
---
```

**预期结果**：开一个新会话问"帮我看看这个 Dockerfile 有没有问题"，模型应自动命中本 Skill。也可以直接 `/container-lint` 手动调用，或 `/container-lint ./api/Dockerfile` 带参数。

**常见问题**：
- *模型总是不触发？* 三个排查方向：①`description` 关键词和用户原话对不上；②确认它出现在"What skills are available?"里；③预算被挤占，跑 `/doctor` 看是否被截断，必要时把低优先级 Skill 在 `skillOverrides` 里设成 `name-only`。
- *触发得太频繁？* 把 `description` 写得更窄，或直接 `disable-model-invocation: true` 改为纯手动。

## 第五步：写正文——动态注入 + 祈使句

正文（第 2 层）有两个杀手锏：**动态上下文注入**和**字符串替换**。

动态注入用 `` !`命令` `` 语法，Claude Code 会在把正文交给模型**之前**先执行命令、用输出替换占位符。这意味着模型看到的不是"去读文件"，而是已经内联好的真实文件内容——决策更准、幻觉更少。

```markdown
## 待审计内容

当前仓库根目录的 Dockerfile：
!`cat Dockerfile 2>/dev/null || echo '(未找到 Dockerfile)'`

参数指定：$ARGUMENTS

## 执行步骤

1. 调用审计脚本，对上述 Dockerfile 做确定性静态分析：
   ```bash
   python3 ${CLAUDE_SKILL_DIR}/scripts/audit_dockerfile.py "$0"
   ```
2. 结合脚本输出，按 `references/best-practices.md` 中的规则逐条解读。
3. 输出三段：问题清单（按严重度排序）、每条的修复 diff、瘦身后预计体积下降。
4. 若发现 secrets 或 root 运行，标记为【高危】，必须修复。
5. 若用户要求"修复"，直接产出改写后的 Dockerfile 并用 Edit 落盘。
```

关键细节：

- `${CLAUDE_SKILL_DIR}` 会展开为本 Skill 目录的绝对路径，无论 Skill 装在个人级、项目级还是插件里都能正确定位脚本。
- `$ARGUMENTS` / `$0` 拿到命令参数；`$0` 是第一个参数（这里是 Dockerfile 路径）。
- 写作风格用**祈使句**（"调用脚本""输出三段"），不要用第二人称（"你应该…"）——这是官方对 AI 消费的明确要求。

**预期结果**：模型收到正文时，`!`cat Dockerfile`` 已经被替换成真实内容，`$0` 已被替换成用户传入的路径，它直接拿到了"带数据的任务书"。

**常见问题**：*`` !`cmd` `` 没生效、被当字面量？* 规则——`!` 必须出现在行首或紧跟空白符之后。写成 `KEY=!`cmd``（`!` 前是等号）不会被识别。多行命令改用 ```! 围栏代码块。另外，注入只跑一遍，命令输出里再含 `` !`` `` 不会被二次展开。

## 第六步：沉淀知识库 references/

把"为什么这么修"的详尽规则放进 `references/best-practices.md`，让 `SKILL.md` 保持精简。这是第 3 层，模型只在需要时才读。

```markdown
# Dockerfile 构建最佳实践

## 镜像体积
- 优先多阶段构建：编译期镜像与运行期镜像分离，最终镜像不含编译器/源码。
- 运行期选 slim/alpine 变体（如 python:3.12-slim），避免用 :latest。
- 合并 RUN 指令减少层数；同一 RUN 内用 `&&` 串联并清理 apt 缓存。

## 安全
- 必须出现非 root 的 `USER` 指令；避免以 root 运行。
- 禁止 `ADD` 远程 URL（用 curl + 校验替代）；优先 COPY。
- 检测 ENV/ARG 中硬编码的密钥、token。

## 可复现性
- 固定基础镜像 tag 与版本；避免 latest。
- package 安装后 pin 版本。

## 缓存友好
- 先 COPY 依赖清单（package.json/requirements.txt）再 COPY 源码，最大化利用层缓存。
```

`SKILL.md` 里只留一句指针："详细规则见 `references/best-practices.md`"。信息要么在 `SKILL.md`，要么在 `references/`，**不要两边重复**。

**预期结果**：`SKILL.md` 正文控制在约 1500 词内，模型触发后上下文清爽；需要解释某条规则时才去读知识库。

## 第七步：封装确定性脚本 scripts/

把"扫描逻辑"交给 Python 而非模型的 token 生成——确定性、可复现、零幻觉，且脚本可不进上下文直接执行，省 token。下面是审计脚本核心（纯标准库，可直接复制）：

```python
#!/usr/bin/env python3
"""Dockerfile 静态审计：输出问题清单，机器可读。"""
import re, sys, json
from pathlib import Path

RULES = {
    "latest_tag":      ("warn",  "使用 :latest，不可复现", "固定具体版本 tag"),
    "no_multistage":   ("warn",  "未见多阶段构建，镜像偏大", "拆分为 builder + runtime 两阶段"),
    "root_user":       ("high",  "未声明非 root USER，以 root 运行", "添加 USER 指令切换非特权用户"),
    "uses_add":        ("warn",  "使用 ADD（可能拉远程或解压）", "改用 COPY 显式控制"),
    "hardcoded_secret":("high",  "疑似硬编码密钥/Token", "改用运行时注入或构建 secret"),
    "apt_no_clean":    ("info",  "apt install 未在同一层清理缓存", "结尾追加 && rm -rf /var/lib/apt/lists/*"),
}

SECRET_PAT = re.compile(r"(?i)(password|passwd|secret|token|api[_-]?key)\s*[:=]\s*\S+")

def audit(text: str):
    lines = text.splitlines()
    findings = []
    has_user = any(l.strip().upper().startswith("USER") for l in lines)
    has_from_as = bool(re.search(r"^FROM\s+\S+\s+AS\s+\S+", text, re.I | re.M))
    for i, l in enumerate(lines, 1):
        s = l.strip()
        if re.match(r"FROM\s+\S+:latest", s, re.I): findings.append((i, "latest_tag"))
        if re.match(r"^ADD\s", s, re.I):            findings.append((i, "uses_add"))
        if re.match(r"^RUN\s+apt", s, re.I) and "rm -rf" not in s:
            findings.append((i, "apt_no_clean"))
        if SECRET_PAT.search(s):                    findings.append((i, "hardcoded_secret"))
    if not has_user:     findings.append((0, "root_user"))
    if not has_from_as:  findings.append((0, "no_multistage"))
    return [
        {"line": ln, "severity": RULES[k][0], "issue": RULES[k][1], "fix": RULES[k][2]}
        for ln, k in findings
    ]

if __name__ == "__main__":
    target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("Dockerfile")
    text = target.read_text(encoding="utf-8") if target.exists() else ""
    if not text:
        print(json.dumps({"error": f"找不到 {target}"}, ensure_ascii=False)); sys.exit(1)
    print(json.dumps({"file": str(target), "findings": audit(text)}, ensure_ascii=False, indent=2))
```

**预期结果**：对一个故意写坏的 Dockerfile，脚本稳定输出 JSON 问题清单，每次结果一致——这就是"确定性"。模型只需解读这份结构化结果，而非自己逐行猜。

**常见问题**：*脚本要不要让模型读进上下文？* 默认不读、直接执行最省 token；只在需要"按环境改脚本"时才让模型读取并打补丁。

## 第八步：配置工具权限 allowed-tools

`allowed-tools` 在 Skill 激活期间**预批准**列出的工具，免去每次确认。本例需要读文件和跑 python：

```yaml
allowed-tools: Read Glob Bash(python3 *)
```

注意它"预批准"而非"限制"——未列出的工具仍可调用，只是会照常走权限流程。要真正禁用某工具，用 `disallowed-tools`。提醒：项目级 Skill 的 `allowed-tools` 在你**信任该工作区**后才生效，克隆陌生仓库前务必先审一眼 `.claude/skills/`，因为 Skill 能给自己授予较宽的工具权限。

## 第九步：测试与迭代——从"会触发"到"效果好"

**会触发 ≠ 好用**。官方文档反复强调要用对照基线验证两点：①该触发的请求是否触发了；②触发后输出是否达标。

**手动验证（三步）**：

1. **自动触发**：新会话里问"审计一下这个 Dockerfile"，看是否命中。
2. **手动触发**：敲 `/container-lint ./Dockerfile`，看参数 `$0` 是否正确传入。
3. **对照基线**：同一批 Dockerfile，分别在"启用 Skill"和"通过 `skillOverrides` 设为 `off` 禁用"的新会话里跑，对比问题发现率。

**用 skill-creator 跑量化 eval**：

```text
/plugin install skill-creator@claude-plugins-official
```

装好后让 Claude 跑评估：`evaluate my container-lint skill with skill-creator`。它会：

- 在 `evals/evals.json` 里存测试用例（prompt + 输入文件 + 期望行为）
- 每个用例起一个隔离子代理跑，记录 token 与耗时
- 按 `grading.json` 的断言判 pass/fail
- 汇总成 `benchmark.json`，对比"启用 vs 禁用"的通过率提升与开销
- 还能做两版 Skill 的盲测 A/B，确认改动是真改进再提交

**预期结果**：你拿到一份量化报告——比如"启用后高危问题检出率从 40% 升到 95%，单次 token 增加 2k"，有数据才敢说 Skill 真的好。

**常见问题**：*Skill 用着用着"失效"了？* 大概率是上下文压缩导致的——压缩后会重新挂载每个 Skill 最近一次调用（合计 25000 token 预算），调用太多时较早的可能被整体丢弃。压缩后重新 `/container-lint` 一次即可恢复全文。

---

## 实战案例：一份可直接落地的完整 Skill

把上述产物拼起来。下面是一个典型"坏 Dockerfile"和本 Skill 的完整交付体验。

输入 `./Dockerfile`：

```dockerfile
FROM python:latest
ADD . /app
WORKDIR /app
RUN pip install -r requirements.txt
RUN apt-get update && apt-get install -y curl
ENV API_KEY=sk-1234567890abcdef
CMD ["python", "main.py"]
```

用户敲 `/container-lint ./Dockerfile`，脚本输出（节选）：

```json
{
  "findings": [
    {"line": 1, "severity": "warn", "issue": "使用 :latest，不可复现", "fix": "固定具体版本 tag"},
    {"line": 2, "severity": "warn", "issue": "使用 ADD", "fix": "改用 COPY"},
    {"line": 6, "severity": "high", "issue": "疑似硬编码密钥/Token", "fix": "改用运行时注入或构建 secret"},
    {"line": 0, "severity": "high", "issue": "未声明非 root USER，以 root 运行", "fix": "添加 USER 指令"},
    {"line": 0, "severity": "warn", "issue": "未见多阶段构建", "fix": "拆分为 builder + runtime"}
  ]
}
```

模型结合知识库给出修复版：

```dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM python:3.12-slim AS runtime
RUN useradd -m appuser
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY . .
USER appuser
CMD ["python", "main.py"]
```

修复点命中：tag 固定、多阶段构建、COPY 替代 ADD、非 root 用户、移除硬编码密钥。预估体积从 ~1GB 降到 ~150MB。

**这套 Skill 为何能成立**，可以用一张架构图说清：

```mermaid
flowchart LR
    U["用户: /container-lint Dockerfile"] --> S["SKILL.md 触发"]
    S -->|"!cat Dockerfile 注入"| D["真实 Dockerfile 内容"]
    S -->|"\$0 = 路径"| P["audit_dockerfile.py 执行"]
    P --> O["结构化问题清单 JSON"]
    D --> M["模型解读"]
    O --> M
    M -->|"查规则"| R["references/best-practices.md<br/>按需加载"]
    M --> OUT["问题清单 + 修复 diff + 预估瘦身"]
```

## 延伸阅读

- **官方 Skills 文档**：`code.claude.com/docs/en/skills` —— frontmatter 字段全集、动态注入、subagent 执行、权限控制的最权威出处。
- **Anthropic 工程博客**：`anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills` —— 三层渐进式披露的设计动机，含 PDF Skill 实例。
- **Agent Skills 开放标准**：`agentskills.io` —— 跨工具可移植的规范与 eval 工作流。
- **官方 skill-development 模板**：`github.com/anthropics/claude-code` 仓库 `plugins/plugin-dev/skills/skill-development/SKILL.md` —— 6 步创建流程与写作风格约束。

## 训练营适用性评估

- **适合类型**：中高级开发者训练营、DevOps/平台工程实战营、AI 辅助研发效能主题。要求学员已具备 Claude Code 基本使用经验。
- **不适合**：纯零基础营——建议先完成 Round 1 的安装与首项目闭环。
- **预计教学时长**：讲解 + 跟做约 90 分钟；若包含 skill-creator eval 闭环与团队分发讨论，扩展到半天工作坊。
- **可延伸作业**：让学员各自实现一个领域 Skill（Terraform 审计、K8s manifest 检查、SQL 注入扫描），统一用 eval 量化评比，形成可复用的团队 Skill 库。

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方 Skills 完整文档，frontmatter 字段、动态上下文注入、权限与 eval 闭环的权威出处。
2. [Equipping agents for the real world with Agent Skills — Anthropic Engineering](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) — 三层渐进式披露架构的设计动机与 PDF Skill 实例。
3. [Skill Development SKILL.md — anthropics/claude-code](https://github.com/anthropics/claude-code/blob/main/plugins/plugin-dev/skills/skill-development/SKILL.md) — 官方 Skill 开发模板，含 6 步创建流程、写作风格约束与校验清单。
4. [Agent Skills 开放标准 — agentskills.io](https://agentskills.io) — 跨平台 Skill 可移植规范与 eval 文件格式。

---

> 本文由「51学通信」原创整理，站长爱卫生（微信：gprshome201101）持续输出通信与 AI 工程实战内容，欢迎交流。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
