首页 › tutorials › 构建Dockerfile智能审计Skill

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

概述

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

前置条件


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

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

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 只影响列表里的显示名,不影响你敲的命令)。我们把它放在个人级目录,所有项目都能用:

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

最终结构:

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 只允许手动触发。
---
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 会在把正文交给模型之前先执行命令、用输出替换占位符。这意味着模型看到的不是"去读文件",而是已经内联好的真实文件内容——决策更准、幻觉更少。

## 待审计内容

当前仓库根目录的 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 落盘。

关键细节:

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

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

第六步:沉淀知识库 references/

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

# 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。下面是审计脚本核心(纯标准库,可直接复制):

#!/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:

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:

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

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

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

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


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

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

输入 ./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,脚本输出(节选):

{
  "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"}
  ]
}

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

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 为何能成立,可以用一张架构图说清:

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 + 预估瘦身"]

延伸阅读

训练营适用性评估

参考来源

  1. Extend Claude with skills — Claude Code Docs — 官方 Skills 完整文档,frontmatter 字段、动态上下文注入、权限与 eval 闭环的权威出处。
  2. Equipping agents for the real world with Agent Skills — Anthropic Engineering — 三层渐进式披露架构的设计动机与 PDF Skill 实例。
  3. Skill Development SKILL.md — anthropics/claude-code — 官方 Skill 开发模板,含 6 步创建流程、写作风格约束与校验清单。
  4. Agent Skills 开放标准 — 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学通信知识星球海报-扫码加入