A negation above its exclusion
Order decides the result, so this line does nothing where it is
.git node_modules !secrets/public.pem secrets/
Check a .dockerignore file. It looks exactly like .gitignore and behaves differently in the one place people rely on, which is re-including a path inside an excluded directory.
Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.
The answer appears here
Paste on the left and press Check. Nothing leaves this tab.
Nothing else to flag.
No formatting problems, and nothing the rules object to. Worth remembering what that covers: this reads the file you pasted, not the account or cluster it will be applied to.
No finding matches that filter.
Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.
Order decides the result, so this line does nothing where it is
.git node_modules !secrets/public.pem secrets/
It is stripped before matching, so it is not the anchor it looks like
.git node_modules /dist
Valid and deliberate, and it excludes the Dockerfile too, which BuildKit honours
.git * !src !package.json !package-lock.json
These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.
The leading-slash anchor stops meaning anything, and negations start working where git ignored them, so the file quietly sends or withholds different things than it reads like.
Instead:Write .dockerignore for the build context. Keep it short, since a build needs far less than a checkout.
Last match wins, so a negation only does work when an exclusion above it matched the same path. Above it, the line does nothing at all.
Instead:Order it exclusion first, negation second, and read the file top to bottom as a sequence rather than a set.
The whole history uploads to the daemon on every build, before the first instruction runs, which reads as a slow build rather than a large context.
Instead:Add .git as the first line.
git will not descend into an excluded directory. Docker evaluates every pattern against every path and takes the last one that matches. Habits carried from one produce surprises in the other.
In .gitignore, secrets/ followed by !secrets/keep.txt does nothing, because git never looks inside secrets/ to find keep.txt. In .dockerignore the same pair works, because matching is per path and the negation is later. People who learned the git rule assume the file is excluded when it is being sent.
Last match wins, so !src above * excludes src and !src below * includes it. Two files with the same lines in a different order behave differently, which is not true of most configuration and is easy to lose in a merge.
Every pattern is already relative to the context root, and the slash is stripped before matching, so /dist and dist are the same. In .gitignore the slash is an anchor that stops the pattern matching in subdirectories, and copying a .gitignore across brings the habit with it.
The whole history is uploaded to the daemon on every build and is never needed in the image. On any repository with age it dominates the time between running the command and the first instruction executing, which reads as Docker being slow.
The classic builder reads the Dockerfile regardless of the ignore file, so excluding it looks harmless. BuildKit honours the exclusion for COPY, so an instruction that copies the Dockerfile in fails with a file-not-found naming a file you can see in the directory.
Which files your context actually holds, so it cannot tell you what is being sent. It reads the patterns, checks their ordering and semantics, and flags the entries most contexts want. Everything runs in your browser.