本教程是「Claude Code 深度使用」系列第 2 轮,聚焦 Skills 开发实战——从零造一个自定义技能。你将动手打造一个 Dockerfile 双体检技能(
dockerfile-audit),它同时做两件事:扫描安全隐患(root 运行、密钥入层、COPY .全量拷贝等)和发现镜像瘦身机会(缺多阶段、肥基础镜像、apt 残留缓存)。学完你能掌握技能的目录结构、frontmatter 配置、确定性脚本 + 动态注入 + 模型上下文判断三层混合写法,以及用官方skill-creator跑评测闭环验证质量。
适用人群:已会用 Claude Code 基本对话、想把自己的重复工作流沉淀成「一行命令技能」的开发者;负责容器安全 / 平台工程的工程师。
前置条件:
${CLAUDE_PROJECT_DIR}、skill-creator 评测、skillOverrides 等都依赖近版本)。python3(脚本只用标准库,无需 pip 安装)。Dockerfile 的练习项目,或跟着本文造一个故意写坏的样例。为什么是「双体检」:纯模型审查容易凭印象下结论、可重复性差;纯规则脚本死板、看不出上下文。本教程的核心设计是 「确定性脚本给骨架,模型补血肉」——脚本跑规则出结构化发现,模型再结合真实 Dockerfile 补充脚本判断不了的点,这是生产级 Skill 最值得掌握的混合范式。
Claude Code 的 Skill 遵循开放的 Agent Skills 标准,本质就是「一个目录 + 一个 SKILL.md 入口」。它与 CLAUDE.md 最大的区别在于 惰性加载:CLAUDE.md 每次会话都进上下文,而技能正文只有在被触发(你敲 /name,或 Claude 判定相关)时才进入上下文。因此长篇参考资料塞进技能几乎不占成本,直到真正用到。
技能可放在三处,决定谁能用:
| 位置 | 路径 | 作用范围 |
|---|---|---|
| 个人级 | ~/.claude/skills/<name>/SKILL.md |
你所有项目 |
| 项目级 | .claude/skills/<name>/SKILL.md |
仅本项目(可提交到版本库共享给团队) |
| 企业级 | 受管设置下发 | 全组织 |
本教程把 dockerfile-audit 放在个人级,这样你在任意含 Dockerfile 的仓库里都能 /dockerfile-audit。
dockerfile-audit/
├── SKILL.md # 入口:frontmatter + 指令(必需)
├── checks.md # 规则细则参考(按需加载,不进主上下文)
└── scripts/
└── audit.py # 确定性扫描脚本(被执行,不被加载进上下文)
要点:SKILL.md 控制在 500 行以内,长细则挪到 checks.md,由主文件按需引用。技能目录建立后会被 Claude Code 实时监听,同会话内增删改即生效,无需重启。
创建骨架:
mkdir -p ~/.claude/skills/dockerfile-audit/scripts
frontmatter 是技能的「说明书」,决定它何时被触发、谁来触发、免审批哪些工具。
---
name: dockerfile-audit
description: 审查 Dockerfile 的安全隐患与镜像瘦身机会。当用户提到 Dockerfile 审查、镜像安全、容器加固、镜像瘦身、减小镜像体积时使用。输出按严重级别排序、带行号与修复命令的清单。
argument-hint: [Dockerfile路径,默认 ./Dockerfile]
disable-model-invocation: true
allowed-tools: Bash(python3 *)
---
逐字段说明(这是写好技能的关键):
description:写清做什么+何时用,把最高频触发词放最前。注意它和 when_to_use 合并在技能列表里被截断到 1536 字符,所以「先放重点」。Claude 是否自动触发技能,几乎全看这段文字。disable-model-invocation: true:本技能是有意触发的审查动作,禁止 Claude 自作主张去跑。这样它只在你敲 /dockerfile-audit 时执行——审查类、部署类、有副作用的工作流都建议这么配。allowed-tools: Bash(python3 *):技能激活期间,Claude 跑 python3 不再逐次问你。注意它只是免审批,不是限制,其它工具仍受你全局权限规则约束。argument-hint:补全时给提示,让使用者知道可以传一个 Dockerfile 路径。⚠️ 安全提醒:项目级技能的
allowed-tools在你信任工作区后才生效。克隆陌生仓库时先看一眼它的.claude/skills/,技能能给自己授予很宽的工具权限。
scripts/audit.py脚本只做纯规则匹配,输出 JSON,保证每次跑同一文件结果一致:
#!/usr/bin/env python3
"""Dockerfile 静态扫描:安全 + 瘦身。仅依赖标准库。"""
import json, re, sys
from pathlib import Path
SEV = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
SECRET = re.compile(r"(?i)(password|passwd|secret|token|api[_-]?key|private[_-]?key|aws_access)")
def audit(path):
lines = Path(path).read_text(errors="replace").splitlines()
findings, has_user, has_hc, stages, bases = [], False, False, 0, []
for i, raw in enumerate(lines, 1):
s, up = raw.strip(), raw.strip().upper()
m = re.match(r"FROM\s+(\S+)", s, re.I)
if m: # FROM 行:版本锁定 + 基础镜像体积
img = m.group(1); stages += 1; bases.append(img)
if ":latest" in img or ("@" not in img and ":" not in img.split("@")[0]):
findings.append({"line": i, "sev": "HIGH", "cat": "security", "rule": "unpinned-base",
"msg": f"基础镜像未锁版本/用 :latest ({img}),应固定到 digest 或明确 tag"})
if re.search(r"(ubuntu|debian|centos|fedora)\b", img, re.I) and "slim" not in img:
findings.append({"line": i, "sev": "MEDIUM", "cat": "size", "rule": "heavy-base",
"msg": f"完整 OS 镜像 ({img}),可换 alpine/distroless/slim"})
if up.startswith("USER "): has_user = True
if up.startswith("HEALTHCHECK"): has_hc = True
if re.match(r"ADD\s+https?://", s, re.I):
findings.append({"line": i, "sev": "LOW", "cat": "security", "rule": "add-url",
"msg": "ADD 远程 URL 不可复现且有供应链风险,改 COPY + 多阶段"})
if re.match(r"COPY\s+\.\s+", s, re.I):
findings.append({"line": i, "sev": "HIGH", "cat": "security", "rule": "copy-dot-all",
"msg": "COPY . 会把 .git/.env/密钥一起带进镜像,配 .dockerignore 并精确指定路径"})
if (up.startswith("ENV ") or up.startswith("ARG ")) and SECRET.search(s):
findings.append({"line": i, "sev": "CRITICAL", "cat": "security", "rule": "secret-in-layer",
"msg": "ENV/ARG 疑似硬编码密钥,构建层会永久留存,改运行期注入"})
if "apt-get install" in s and "--no-install-recommends" not in s:
findings.append({"line": i, "sev": "LOW", "cat": "size", "rule": "apt-recommends",
"msg": "未加 --no-install-recommends,装入推荐包"})
if "apt-get install" in s and not any("rm -rf /var/lib/apt" in l for l in lines[i:i+5]):
findings.append({"line": i, "sev": "MEDIUM", "cat": "size", "rule": "apt-no-clean",
"msg": "apt 安装后未同层清理 /var/lib/apt/lists,缓存烤进镜像"})
if re.match(r"apt-get\s+(upgrade|dist-upgrade)", s, re.I):
findings.append({"line": i, "sev": "MEDIUM", "cat": "size", "rule": "apt-upgrade",
"msg": "apt-get upgrade 让层不可复现且增大体积,交给基础镜像维护"})
if not has_user:
findings.append({"line": 0, "sev": "CRITICAL", "cat": "security", "rule": "runs-as-root",
"msg": "默认以 root 运行,增加 USER 指令指定非 root 用户"})
if not has_hc:
findings.append({"line": 0, "sev": "LOW", "cat": "security", "rule": "no-healthcheck",
"msg": "缺少 HEALTHCHECK,编排器无法感知真实健康"})
if stages <= 1:
findings.append({"line": 0, "sev": "MEDIUM", "cat": "size", "rule": "no-multistage",
"msg": "未多阶段构建,编译器/构建工具残留进最终镜像"})
findings.sort(key=lambda f: (SEV[f["sev"]], f["line"]))
return findings
if __name__ == "__main__":
p = sys.argv[1] if len(sys.argv) > 1 else "Dockerfile"
print(json.dumps({"file": p, "findings": audit(p)}, ensure_ascii=False, indent=2))
预期结果:对一个写坏的 Dockerfile,会吐出按 CRITICAL→HIGH→MEDIUM→LOW 排好序的 JSON 数组,每条带 line / sev / cat / rule / msg。
常见问题:
- 脚本报 FileNotFoundError:传入的 Dockerfile 路径不对,确认相对/绝对路径。
- 输出太多噪音:调高阈值——把 LOW 过滤掉,或细化正则(如 apt-no-clean 的前瞻窗口从 5 行扩到 8 行)。
这一步是技能质量的核心。正文要做三件事:动态注入目标文件 → 跑脚本拿骨架 → 让模型补血肉。
# Dockerfile 安全 + 瘦身双体检
对给定 Dockerfile 做静态扫描并给出可执行修复建议。审查以确定性脚本结果为准,再补充脚本判断不了的上下文点。
## 目标文件
!`cat $ARGUMENTS 2>/dev/null || cat Dockerfile`
## 第一步:跑确定性扫描
执行内置脚本,得到结构化发现(JSON):
```bash
python3 ${CLAUDE_SKILL_DIR}/scripts/audit.py "${CLAUDE_PROJECT_DIR}/$ARGUMENTS"
```
## 第二步:补充上下文判断
脚本只做规则匹配。请结合上面读到的真实 Dockerfile 内容,补充脚本无法判断的问题,至少检查:
- WORKDIR 是否是写死的绝对路径、是否配合 chown 避免权限坑;
- ENTRYPOINT/CMD 是否暴露了调试端口或 shell 注入面(如 `sh -c` 直接拼变量);
- 多个 RUN 是否能合并成一层以减少镜像层数;
- .dockerignore 是否配套(如脚本里命中 copy-dot-all,务必点名提醒)。
## 输出格式
严格按下表结构输出,按严重级别排序,每条含:级别、类别(安全/瘦身)、所在行、规则名、问题、**可直接执行的修复命令或片段**。最后给一段「优先修前三项」的总结,并估算瘦身收益(如「换 alpine + 多阶段预计 -180MB」)。
完整规则细则见 [checks.md](checks.md)。
三个语法点务必吃透:
!`cat Dockerfile` 动态注入:在技能正文送给模型之前,Claude Code 先执行该命令,把输出原地替换进文本。模型看到的是真实文件内容而非命令本身——这让审查「接地气」。它只替换一次,且要求 ! 出现在行首或紧跟空白。${CLAUDE_SKILL_DIR}:指向技能自己的目录,使 audit.py 无论装在个人级、项目级还是插件里都能被正确引用。$ARGUMENTS:你敲 /dockerfile-audit api/Dockerfile 时,api/Dockerfile 就替换进来。把每条规则背后的「为什么、修成什么样」写成样例,挂在 checks.md,由 SKILL.md 引用。这样主上下文只留骨架,模型只在需要时按需读细则——这就是官方强调的「渐进式披露(progressive disclosure)」。片段示例:
## runs-as-root (CRITICAL, security)
**为何危险**:容器以 uid 0 运行,逃逸即获宿主 root。
**修复**:
RUN groupadd -r app && useradd -r -g app app
USER app
保存文件后无需重启(实时监听生效)。进入任意含 Dockerfile 的项目,启动 claude,直接:
/dockerfile-audit
或显式指定文件:
/dockerfile-audit services/api/Dockerfile
因为设了 allowed-tools: Bash(python3 *),跑 audit.py 时不会再弹权限确认。审查结果会按级别排序、带行号、带修复命令地输出。
常见问题:
- /dockerfile-audit 不出现:frontmatter YAML 写错会以「空元数据」加载,/名字 仍能跑但 Claude 拿不到 description 去自动匹配。用 claude --debug 启动可看到解析报错。
- 技能触发太频繁(若你关掉 disable-model-invocation):把 description 写更窄,或干脆保留 disable-model-invocation: true 只手动触发。
- 技能列表太长,描述被截断:跑 /doctor 看哪些描述被砍。低优先级的可在 skillOverrides 设为 "name-only" 给别的技能腾预算。
把下面这份故意写坏的文件存为 ./Dockerfile:
FROM ubuntu:latest
ENV DB_PASSWORD=s3cret
RUN apt-get update && apt-get install -y python3 pip curl vim
COPY . /app
RUN pip install -r /app/requirements.txt
CMD ["python3", "/app/main.py"]
执行 /dockerfile-audit,你将得到类似下面的报告(脚本 + 模型合议):
| 级别 | 类别 | 行 | 规则 | 问题 | 修复 |
|---|---|---|---|---|---|
| CRITICAL | 安全 | 2 | secret-in-layer | DB_PASSWORD 烤进层 |
改运行期注入 -e/secret |
| CRITICAL | 安全 | — | runs-as-root | 默认 root | 增加 USER app |
| HIGH | 安全 | 1 | unpinned-base | ubuntu:latest |
锁 python:3.12-slim |
| HIGH | 安全 | 4 | copy-dot-all | COPY . 全量拷贝 |
配 .dockerignore,精确 COPY |
| MEDIUM | 瘦身 | 3 | apt-no-clean | 未清缓存 | 同层 rm -rf /var/lib/apt/lists/* |
| MEDIUM | 瘦身 | 3 | apt-recommends | 装入推荐包 | 加 --no-install-recommends |
| MEDIUM | 瘦身 | — | no-multistage | 无多阶段 | 拆 builder/运行两阶段 |
| LOW | 瘦身 | — | no-healthcheck | 缺探活 | 加 HEALTHCHECK CMD ... |
结论:优先修前三项(密钥入层、root 运行、COPY .);改用 python:3.12-slim + 多阶段 + 清缓存,预计镜像从约 420MB 降到 130MB。
flowchart TD
A["你敲 /dockerfile-audit api/Dockerfile"] --> B["Claude Code 渲染 SKILL.md"]
B --> C{"动态注入 !`cat Dockerfile`"}
C -->|替换为真实文件内容| D["$ARGUMENTS → api/Dockerfile"]
B --> E["模型读正文指令"]
E --> F["跑 audit.py<br/>确定性规则匹配"]
F --> G["输出 JSON 发现"]
G --> H["模型结合真实文件<br/>补充上下文判断"]
H --> I["按级别排序输出<br/>带行号+修复命令"]
C -.失败/默认.-> J["cat Dockerfile"]
style C fill:#fef3c7
style F fill:#dbeafe
style H fill:#dcfce7
「技能被触发」只说明 Claude 找到了它,不等于它做对了。要量化质量,用官方 skill-creator 插件做基线对比:
/plugin install skill-creator@claude-plugins-official
/reload-plugins
随后让它评测你的技能:
请用 skill-creator 评测我的 dockerfile-audit 技能
它会:准备一批测试 prompt(含坏/好 Dockerfile 与期望断言)存到技能目录 evals/evals.json;每个 case 起一个干净上下文子代理分别跑「有技能 / 无技能」;按断言判分写 grading.json;最后汇总成 benchmark.json,给出启用技能后通过率提升了多少,以及相应的 token/耗时开销。改一版 frontmatter 或脚本就重跑一次 A/B,确认「是真改进」再提交——这是把技能从「能用」做到「好用」的标准动作。
code.claude.com/docs/en/skills——frontmatter 全字段、子代理执行、动态注入的权威说明。agentskills.io——跨工具通用的技能规范与评测方法。github.com/anthropics/claude-plugins-official——评测/基准/版本对比/描述调优的官方工具。audit.py 接进 pre-commit 或流水线)。audit.py 增加 --format sarif 输出,对接 GitHub Code Scanning;② 把 disable-model-invocation 关掉,对比自动触发命中率,用 skill-creator 调 description;③ 把技能提交到项目级 .claude/skills/ 并写 .dockerignore 自动生成子技能。[!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 等场景全覆盖。
