You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(ci-testing): two patterns from a published-the-wrong-image incident
Both come from the same afternoon in netresearch/phpmyadmin-docker-compose-stack.
Pattern 7 — a build step carried no `target:`, which had been harmless while
the Dockerfile had one final stage. Appending an nginx stage after the runtime
silently redirected that build to it, and php-fpm:latest was published as nginx.
The smoke test that would have caught it lives in a separate job, and a
pull-request build never leaves its runner, so it only runs on the default
branch - after publishing. The pattern therefore pairs the explicit target with
an identity assertion in the job that builds the image, and insists the
assertion be run against both images so it is known to be able to fail.
Pattern 8 — a gate compared scan results with `grep -Fxv -f`, on the reasoning
that an empty pattern file matches nothing and every finding therefore counts as
new. That holds for GNU grep on the host and is inverted in busybox, which the
Alpine-based CI image ships: the gate silently passed instead. The empty
baseline is the normal state whenever the reference scan is clean, so this was
the likely case rather than an edge one.
Pattern 7 opens by distinguishing itself from Pattern 5: both say "target" and
mean different things - a bake target inheriting metadata there, the Dockerfile
stage a build selects here.
SKILL.md is untouched: it sits exactly on the 500-word cap.
Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
Copy file name to clipboardExpand all lines: skills/docker-development/references/ci-testing.md
+121Lines changed: 121 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -178,3 +178,124 @@ When smoke/boot-testing an image by hand (not in the CI matrix):
178
178
- **Foreground apps that log to a file leave `docker logs` empty.** E.g. Tomcat started with `-fg` writes to `logs/catalina.out`, not stdout — an empty `docker logs` does *not* mean "nothing happened". Read the in-container log files (`docker exec <c> sh -c 'tail -n 80 .../catalina.out'`), and check the process and state (`docker inspect -f '{{.State.Status}} {{.State.ExitCode}}' <c>`).
179
179
- **Minimal/distroless images have no shell.** `docker exec … sh`/`tail`/`pgrep` won't exist on `scratch`/distroless runtimes — probe with host-side `curl` against a published port, `docker inspect` for state, or a debug sidecar (`docker run --rm --pid container:<c> busybox …`).
180
180
- **Grep for real failure signals, not benign noise.** After a bundled-dependency swap, scan logs for `NoSuchMethodError|AbstractMethodError|LinkageError|IncompatibleClassChangeError` (binary incompatibility) — not bare `ClassNotFoundException`, which OSGi/plugin frameworks emit normally.
181
+
182
+
## Pattern 7: A build without `target:` builds whatever stage comes last
183
+
184
+
Not to be confused with Pattern 5: that `target` is a bake target inheriting
185
+
metadata, this one is the Dockerfile stage a build selects. Same word, different
186
+
thing, and reading one does not cover the other.
187
+
188
+
### Problem
189
+
190
+
A repository publishes one image from a multi-stage Dockerfile. The build step
191
+
names no target, because there was only ever one final stage:
192
+
193
+
```yaml
194
+
- uses: docker/build-push-action@…
195
+
with:
196
+
context: .
197
+
tags: ${{ steps.meta.outputs.tags }} # no target:
198
+
```
199
+
200
+
Later a second image is added — an nginx to front the php-fpm one — as a stage
201
+
appended after the existing runtime. Nothing in the original build changed, yet
202
+
it now publishes the *new* stage under the *old* name: Docker builds the last
203
+
stage in the file when no target is given.
204
+
205
+
The failure is silent at build time and loud much later. `php-fpm:latest` was an
206
+
nginx image for an hour; the first symptom was a sidecar dying on
207
+
`exec: "/bin/bash": no such file or directory`.
208
+
209
+
### Fix
210
+
211
+
Name the target explicitly in every build, including the one that was there
212
+
first:
213
+
214
+
```yaml
215
+
- uses: docker/build-push-action@…
216
+
with:
217
+
context: .
218
+
target: runtime # not "whatever is last"
219
+
```
220
+
221
+
Then assert what was actually built, in the job that builds it. A smoke test in
222
+
a separate job cannot help here: on a pull request the image never leaves the
223
+
build runner, so that job only runs on the default branch — after publishing.
0 commit comments