The Problem: `sed -i` and Bind Mounts Don't Mix

You've edited a configuration file on your host machine using sed -i. You verify the changes on disk. You expect your Docker container, which uses a bind mount to access this file, to pick up the updates. But it doesn't. This common frustration stems from a subtle interaction between how bind mounts work and how the sed -i command modifies files.

The core issue is the inode. When you use sed -i, it doesn't always modify the file in place. Instead, it often creates a new temporary file, writes the modified content to it, and then atomically replaces the original file with the new one. This replacement operation changes the inode number of the file on the host filesystem.

Docker containers, when using bind mounts, establish a direct link to the specific inode of the file on the host at the time the mount is created. If the inode changes on the host due to an operation like sed -i's replacement, the container's bind mount is still pointing to the old, now-deleted inode. The container sees a file that appears to be the same path, but it's a different underlying file with a new inode. The container's view of the file therefore remains unchanged, even though the path on the host now points to a new file with the same name.

Consider a scenario where you're running Prometheus. Its configuration file, prometheus.yml, is bind-mounted from the host into the container. You need to adjust a scrape configuration. You run sed -i 's/some_value/new_value/g' prometheus.yml on your host. The file on your host now correctly reflects new_value. However, Prometheus inside the container continues to use the old configuration. Prometheus hasn't been updated because the container is still referencing the original file inode that existed when the bind mount was established. The sed -i operation, by creating a new file and updating the directory entry, effectively swapped out the underlying data structure the container was looking at, but the container itself is still attached to the ghost of the old inode.

Understanding Inodes and Bind Mounts

An inode, short for index node, is a data structure on a Unix-like filesystem that stores all information about a file or directory, except its name and actual data content. This includes metadata like permissions, ownership, timestamps, and crucially, the location of the file's data blocks on the disk. Every file and directory on a filesystem has a unique inode number.

When you create a bind mount in Docker (e.g., -v /host/path/to/file:/container/path/to/file), Docker establishes a direct mapping. The container's filesystem view at /container/path/to/file points directly to the inode of /host/path/to/file on the host's filesystem. This provides efficient access, as there's no copying involved; the container accesses the file as if it were on the host.

The problem arises because sed -i, for safety and atomicity, often employs a strategy where it writes changes to a temporary file. Once the temporary file is successfully written and synced, it then performs an atomic rename() operation. The rename() system call is key here: on many filesystems, renaming a file within the same filesystem does not change the inode; it just updates the directory entry. However, if the temporary file is on a different filesystem, or if the filesystem implementation handles it differently, the rename() might effectively replace the original file with the new one, leading to a new inode being assigned to the path. More commonly, sed -i creates a backup file (e.g., prometheus.yml.bak) and then replaces the original file with the modified content, which can result in a new inode.

The container's mount point is tied to the original inode. When the inode changes on the host, the container is left referencing a file that no longer exists in the filesystem's inode table, or more accurately, it's referencing the *old* inode. The new file with the new inode, even if it has the same name, is invisible to the container's bind mount.

Referenced Sources

Share this intelligence