Skip to content

Commit e395a2a

Browse files
committed
docs(docker): enforce build cache reuse
- Add a non-negotiable Docker build cache contract - Distinguish cache boundaries from layer-count minimization - Add container cache evaluations and review guidance
1 parent 5d7f24b commit e395a2a

5 files changed

Lines changed: 197 additions & 10 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ The dependency-free [Agent Skills eval harness](evals/) validates every skill an
555555

556556
The initial suites cover:
557557

558+
- [Containers and orchestration evals](skills/containers-orchestration/evals/evals.json)
558559
- [Core engineering evals](skills/core-engineering/evals/evals.json)
559560
- [Cloudflare WAF author evals](skills/cloudflare-waf-author/evals/evals.json)
560561
- [IAM security advisor evals](skills/iam-security-advisor/evals/evals.json)

rules/440-docker.mdc

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,25 @@ ENTRYPOINT ["/main"]
136136

137137
## Layer Optimization
138138

139+
### Build Cache Contract (NON-NEGOTIABLE)
140+
141+
> [!IMPORTANT]
142+
> Optimize cache invalidation, not the raw number of layers. Intentional layers are the unit of cache reuse. Do not collapse stable and frequently changing work into one `RUN` or `COPY` merely to reduce the layer count.
143+
144+
Every Dockerfile change must satisfy this gate:
145+
146+
- Copy dependency manifests and lock files before application source, then install dependencies in that stable layer.
147+
- Do not use broad `COPY . .` before dependency installation unless the build demonstrably requires source code for dependency resolution.
148+
- Include a `.dockerignore` that excludes source-control metadata, local dependencies, build output, caches, temporary files, and secrets.
149+
- Use multi-stage builds when build tools or intermediate artifacts are not required at runtime; copy only required runtime artifacts into the final stage.
150+
- Combine logically coupled package-manager operations, such as `apt-get update`, installation, and cleanup, in the same `RUN`.
151+
- **Treat BuildKit cache mounts as performance-only.** Use them for expensive package-manager or compiler caches when supported, but require the build to succeed when the cache is empty or garbage-collected.
152+
- Verify reuse with BuildKit plain-progress output or equivalent CI evidence. A repeated build with unchanged dependency inputs must report the dependency copy and installation steps as cached. Do not use `--no-cache` for this check.
153+
154+
If the image cannot be built in the current environment, report the exact blocker and review the Dockerfile statically against this gate. Do not claim cache reuse was verified.
155+
156+
Current Docker guidance: [Optimize cache usage](https://docs.docker.com/build/cache/optimize/) and [Build cache invalidation](https://docs.docker.com/build/cache/invalidation/).
157+
139158
### Order Instructions by Change Frequency
140159
```dockerfile
141160
# GOOD - Least changing first, most changing last
@@ -159,9 +178,9 @@ COPY . . # Invalidates cache on every code change
159178
RUN pip install -r requirements.txt
160179
```
161180

162-
### Combine RUN Commands to Reduce Layers
181+
### Combine Logically Coupled RUN Operations
163182
```dockerfile
164-
# GOOD - Single layer
183+
# GOOD - Package index, installation, and cleanup share one cache boundary
165184
RUN apt-get update && \
166185
apt-get install -y --no-install-recommends \
167186
ca-certificates \

skills/containers-orchestration/SKILL.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ CMD ["python", "-m", "src.main"]
114114

115115
## Layer Optimization
116116

117+
### Build Cache Gate (Non-Negotiable)
118+
119+
Optimize cache invalidation rather than minimizing the number of layers:
120+
121+
- Copy dependency manifests and lock files before application source.
122+
- Install dependencies before broad source `COPY` instructions.
123+
- Require `.dockerignore` coverage for volatile, generated, sensitive, and irrelevant files.
124+
- Use multi-stage builds and copy only required runtime artifacts.
125+
- Combine logically coupled package index, installation, and cleanup operations in one `RUN`.
126+
- **Treat BuildKit cache mounts as performance-only.** Use them where supported, but require the build to succeed when the cache is empty or garbage-collected.
127+
- Verify a repeated BuildKit build reports unchanged dependency copy and installation steps as cached. If building is unavailable, report that limitation instead of claiming verification.
128+
129+
Do not collapse stable dependency work and frequently changing application work merely to reduce the layer count. See the canonical [Docker rule](../../rules/440-docker.mdc#build-cache-contract-non-negotiable) and [Docker cache guidance](https://docs.docker.com/build/cache/optimize/).
130+
117131
### Order Instructions by Change Frequency
118132

119133
```dockerfile
@@ -337,9 +351,10 @@ CMD ["node", "index.js"]
337351
- [ ] Use multi-stage builds for compiled languages
338352
- [ ] Run as non-root user
339353
- [ ] Use minimal base images (alpine, slim, distroless)
340-
- [ ] Order instructions by change frequency
341-
- [ ] Combine RUN commands to reduce layers
342-
- [ ] Add .dockerignore file
354+
- [ ] Build cache gate passed with dependency inputs before application source
355+
- [ ] Repeated build confirms unchanged dependency steps are cached, or limitation reported
356+
- [ ] Combine logically coupled package-manager operations in one RUN
357+
- [ ] Add `.dockerignore` file
343358
- [ ] Include HEALTHCHECK instruction
344359
- [ ] Clean up package manager caches
345360
- [ ] Don't install unnecessary packages
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
{
2+
"skill_name": "containers-orchestration",
3+
"evals": [
4+
{
5+
"id": 1,
6+
"prompt": "Review this Dockerfile for build-cache behavior and provide the smallest safe rewrite:\n\nFROM node:24-alpine\nWORKDIR /app\nCOPY . .\nRUN npm ci\nRUN npm run build\nCMD [\"node\", \"dist/server.js\"]\n\nThe repository has package.json, package-lock.json, src/, tests/, node_modules/, and a .git directory. The final image does not need the compiler or development dependencies. State how to verify cache reuse.",
7+
"expected_output": "Identify the broad source copy before dependency installation as the primary cache problem. Copy package manifests first, install dependencies, copy source later, add a .dockerignore, use a multi-stage build that copies only runtime artifacts, and verify a repeated BuildKit build reports dependency steps as cached.",
8+
"files": [],
9+
"checks": [
10+
{
11+
"id": "manifests-before-source",
12+
"type": "contains_all",
13+
"values": [
14+
"package.json",
15+
"package-lock.json",
16+
"COPY"
17+
]
18+
},
19+
{
20+
"id": "requires-dockerignore",
21+
"type": "contains",
22+
"value": ".dockerignore"
23+
},
24+
{
25+
"id": "uses-multi-stage",
26+
"type": "contains_any",
27+
"values": [
28+
"multi-stage",
29+
"AS builder",
30+
"FROM node:24-alpine AS"
31+
]
32+
},
33+
{
34+
"id": "copies-runtime-artifacts",
35+
"type": "contains_any",
36+
"values": [
37+
"COPY --from",
38+
"runtime artifacts",
39+
"final stage"
40+
]
41+
},
42+
{
43+
"id": "verifies-cache",
44+
"type": "contains_all",
45+
"values": [
46+
"BuildKit",
47+
"CACHED",
48+
"repeated"
49+
]
50+
}
51+
]
52+
},
53+
{
54+
"id": 2,
55+
"prompt": "A reviewer says every Dockerfile should have the fewest possible layers, so all dependency installation, compilation, cleanup, and application copying should be collapsed into one giant RUN instruction. Is that the correct optimization target? Give the non-negotiable rule and a corrected approach.",
56+
"expected_output": "Reject raw layer-count minimization. Explain that intentional layers are cache boundaries, keep stable dependency work before frequently changing source, combine only logically coupled package-manager operations, and preserve correctness when BuildKit caches are empty.",
57+
"files": [],
58+
"checks": [
59+
{
60+
"id": "rejects-layer-count-target",
61+
"type": "contains_any",
62+
"values": [
63+
"not the correct",
64+
"not the goal",
65+
"do not optimize for",
66+
"not layer count"
67+
]
68+
},
69+
{
70+
"id": "preserves-cache-separation",
71+
"type": "contains_all",
72+
"values": [
73+
"cache",
74+
"separate"
75+
]
76+
},
77+
{
78+
"id": "keeps-stable-work-early",
79+
"type": "contains_all",
80+
"values": [
81+
"dependencies",
82+
"source"
83+
]
84+
},
85+
{
86+
"id": "combines-only-coupled-operations",
87+
"type": "contains_all",
88+
"values": [
89+
"logically coupled",
90+
"install",
91+
"cleanup"
92+
]
93+
},
94+
{
95+
"id": "cache-not-correctness",
96+
"type": "contains_any",
97+
"values": [
98+
"empty cache",
99+
"cache is empty",
100+
"cache contents"
101+
]
102+
}
103+
]
104+
},
105+
{
106+
"id": 3,
107+
"prompt": "Review a Dockerfile's caching strategy in an environment where Docker and BuildKit are unavailable. The file copies requirements.txt before src/ and installs dependencies between those copies. Report what can and cannot be concluded.",
108+
"expected_output": "Perform a static review that recognizes the cache-friendly ordering, check other contract items such as .dockerignore and coupled package operations, and explicitly state that runtime cache reuse was not verified because BuildKit was unavailable.",
109+
"files": [],
110+
"checks": [
111+
{
112+
"id": "recognizes-static-ordering",
113+
"type": "contains_all",
114+
"values": [
115+
"requirements.txt",
116+
"src",
117+
"cache"
118+
]
119+
},
120+
{
121+
"id": "reports-limitation",
122+
"type": "contains_all",
123+
"values": [
124+
"BuildKit",
125+
"unavailable",
126+
"not verified"
127+
]
128+
},
129+
{
130+
"id": "does-not-claim-runtime-proof",
131+
"type": "not_regex",
132+
"pattern": "(?i)(cache reuse (?:is|was) verified|verified cache reuse)"
133+
}
134+
]
135+
}
136+
]
137+
}

skills/containers-orchestration/references/docker.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@ ENTRYPOINT ["/main"]
131131

132132
## Layer Optimization
133133

134+
### Non-Negotiable Build Cache Contract
135+
136+
Dockerfiles must preserve intentional cache boundaries:
137+
138+
- Copy dependency manifests and lock files before application source.
139+
- Install dependencies before broad source copies.
140+
- Use `.dockerignore` to keep volatile, generated, sensitive, and irrelevant files out of the build context.
141+
- Use multi-stage builds when build-only tools or artifacts are unnecessary at runtime.
142+
- Combine logically coupled package index, installation, and cleanup operations.
143+
- **Treat BuildKit cache mounts as performance-only.** Use them where supported, but require builds to succeed when the cache is empty or garbage-collected.
144+
- Verify repeated builds report unchanged dependency steps as cached, or explicitly report why runtime verification was unavailable.
145+
146+
Do not optimize for the fewest possible layers. Stable dependency work and frequently changing application work should remain separate when that improves cache reuse. See Docker's current [cache optimization](https://docs.docker.com/build/cache/optimize/) and [cache invalidation](https://docs.docker.com/build/cache/invalidation/) guidance.
147+
134148
### Order Instructions by Change Frequency
135149

136150
```dockerfile
@@ -155,10 +169,10 @@ COPY . . # Invalidates cache on every code change
155169
RUN pip install -r requirements.txt
156170
```
157171

158-
### Combine RUN Commands to Reduce Layers
172+
### Combine Logically Coupled RUN Operations
159173

160174
```dockerfile
161-
# ✅ GOOD - Single layer
175+
# ✅ GOOD - Package index, installation, and cleanup share one cache boundary
162176
RUN apt-get update && \
163177
apt-get install -y --no-install-recommends \
164178
ca-certificates \
@@ -754,9 +768,10 @@ docker port <container_id>
754768
- [ ] Use multi-stage builds for compiled languages
755769
- [ ] Run as non-root user
756770
- [ ] Use minimal base images (alpine, slim, distroless)
757-
- [ ] Order instructions by change frequency
758-
- [ ] Combine RUN commands to reduce layers
759-
- [ ] Add .dockerignore file
771+
- [ ] Build cache gate passed with dependency inputs before application source
772+
- [ ] Repeated build confirms unchanged dependency steps are cached, or limitation reported
773+
- [ ] Combine logically coupled package-manager operations in one RUN
774+
- [ ] Add `.dockerignore` file
760775
- [ ] Include HEALTHCHECK instruction
761776
- [ ] Clean up package manager caches
762777
- [ ] Don't install unnecessary packages

0 commit comments

Comments
 (0)