Docker mounts the file, not the filename
One of our containers gets a tool script through a Docker bind mount, a single file mounted from the host. We shipped a fixed version of that file, watched the copy land on the host, and moved on. The container ran the old version for a full day.
The host showed the new content at that path. The container, same path, showed the old content. Both were telling the truth.
The mount is bolted to the inode
On Linux, a filename is just a label pointing at an inode, the actual stored file. A single-file bind mount does not grab the label. It grabs the inode that label pointed at when the container started.
Here is the part that bites: almost every normal way of deploying a file replaces the inode. mv from a temp file, install, rsync, and most editors' saves all write a new file and swap the label over to it. The label now points at new content. The container's mount is still bolted to the old inode, which the kernel keeps alive because something still holds it.
The hose is clamped to the bucket, not to the spot the bucket sits on. Swap in a fresh bucket and the hose is still clamped to the old one you carried out back.
It gets worse. Once the swap has happened, even an in-place edit on the host path writes the new inode, so no amount of editing that file reaches the container. And our release copy dropped the script's execute bit on top of it, a second silent breaker riding along with the first.
The fix: mount the directory, or recreate on deploy
Two real options.
Mount the parent directory instead of the file. Directory mounts resolve names on every open, so a swapped file inside the directory shows up in the container immediately. This is the durable fix, and what we moved to.
If you must mount a single file, force-recreate the consuming container on every deploy of that file. Not docker restart, which can keep the old mount. docker compose up -d --force-recreate rebinds the mount to the current inode.
And either way, the verification rule this taught us: after deploying anything into a mounted path, read the file from inside the container. docker exec, cat the path, and diff it against what you shipped. The host copy proves nothing. The only view that counts is the one the process actually has.
The day we lost was not the kernel being weird. The kernel was doing exactly what a bind mount promises. We just aimed the promise at a label and assumed it meant the file.