Step 1 of 5 · Reading · ~3 min
Read
Filesystems & Images
Copy-on-Write
The layers below a container are read-only and shared by every container built on them. So what happens the first time one of those containers writes to /etc/hostname? It cannot edit the shared file, and it cannot be told no. OverlayFS resolves that with the oldest trick in systems programming: let the write happen somewhere else.
The promotion
read /etc/hostname -> not in upper; found in the nginx layer; served read-only
write /etc/hostname -> 1. copy the whole file from its lower layer into upper
2. apply the write to the upper copy
3. every later read and write uses upper
The file has been promoted. Its lower original is untouched and still serving the other nine hundred containers on the host; this container has its own, and from now on the lower copy is simply never consulted for that path.
Three consequences follow directly, and they are the ones that show up in production:
- Starting a container copies nothing. The upper layer begins empty, which is why start time is milliseconds regardless of image size.
- The first write to a large file costs a full copy of it. Appending one line to a 2 GB database file inside a container copies 2 GB before the append. Writes after that are ordinary file I/O.
- Copy-up is per file, not per block. There is no partial promotion; touching one byte promotes the whole thing.
Where the writes go
The upper layer is the container layer: one per container, stored under /var/lib/docker, and its lifetime is the container's, not the image's. docker stop keeps it — the container's writes are still there when you start it again. docker rm deletes it, and that is the moment the data is gone. "I stopped the container and lost my data" is nearly always "I removed the container".
Two limits fall out of that. N containers from one image means N upper layers, all on the host's disk, all invisible to docker images. And a write-heavy workload can fill /var/lib/docker with nothing showing up as an image — the classic three-in-the-morning disk-full page.
The fix is to keep writes out of the overlay entirely. A volume (-v data:/var/lib/postgresql) is a plain directory bind-mounted over that path: it bypasses overlayfs, has no copy-up cost, and survives docker rm. Databases, uploads and logs belong in volumes; the container layer is for the incidental writes a program makes and never reads again.
Why the exercise is a simulator
Mounting an overlay needs CAP_SYS_ADMIN, which no submission here has. The mechanism, though, is two maps and a preference order, and writing it makes the timing behaviour above obvious rather than mysterious.
Your exercise: Copy-on-Write Tracking
| The overlay does | Your simulator does |
|---|---|
| a file shipped in a read-only image layer | LOWER <path> <content> |
a read: upper first, then lower, then ENOENT | READ <path> |
| a write: promote to upper if needed, then write there | WRITE <path> <content> — always ends in upper |
the container layer on disk, the thing docker rm deletes | LIST-UPPER — upper paths, sorted |
| which layer is actually serving this path right now | STAT <path> — upper <content> or lower <content> |
WRITE promotes unconditionally, including for a path that exists in no lower layer — creating a new file and modifying an inherited one both end with the path in upper, which is why LIST-UPPER is a truthful list of everything this container has changed. Note what WRITE never does: it does not modify lower. If your READ starts returning a written value out of the lower map, you have shared what containers must not share.
Content in these commands is a single token, so a straightforward split on spaces is enough; keep the path and the content apart with a bounded split rather than assuming a fixed token count.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…