arthrop0d

Brief articles on varied topics.

Copy-Up in Container Overlay Filesystems

containerslinuxperformance

A copy-on-write overlay filesystem lets a container see several filesystem layers as one directory tree. The image layers are read-only; a separate writable layer records the container’s changes. When a process modifies a file that exists only in a read-only layer, the filesystem copies that file into the writable layer first, then applies the change there.

This operation is called copy-up.

The problem: reusable images without shared writes

A container image is usually built from layers. One layer might contain a distribution’s base files, another might add a runtime, and a later layer might add the application. These layers are useful precisely because they can be shared: many containers can use the same image data without storing another complete copy for each container.

But a running container needs to behave like a normal filesystem. Its process may create logs, update a database, rewrite a configuration file, or remove a file. The image layers cannot be changed, because other containers may be using them and because image layers are intended to remain immutable.

Without an overlay filesystem, the runtime would need to make a complete private copy of the image for each container, or arrange a separate writable filesystem and copy files into it manually. Either approach wastes storage or makes starting a container more complicated. Overlaying a writable layer on top of read-only layers provides the normal filesystem view while preserving the shared image data.

One merged view, several directories

On Linux, this is commonly implemented with OverlayFS, a kernel filesystem that combines directories into one mounted view. Its typical configuration has three important parts:

There is also a working directory used internally by OverlayFS while it performs some operations. It must be on the same filesystem as the upper directory.

Suppose the layers contain these paths:

lower layer:  /etc/app.conf
lower layer:  /usr/bin/service
upper layer:  /var/log/app.log

Through the merged view, all three paths appear to exist. The process does not normally know which layer supplied each one.

For a path present in the upper layer, the upper copy wins. If it is absent there, OverlayFS searches the lower layers, from the most specific or newest layer toward older ones. This is why a later image layer can replace a file from an earlier layer without modifying the earlier layer.

What happens on a write

Consider a file called /etc/app.conf that exists only in a read-only lower layer. A process opens it for writing and changes one setting. Conceptually, the sequence is:

  1. OverlayFS resolves /etc/app.conf in the merged view and finds it in a lower layer.
  2. Because the operation would modify the file, OverlayFS performs copy-up.
  3. It creates /etc/app.conf in the upper layer, copying the file’s contents and relevant metadata such as permissions and ownership.
  4. The write is performed on the upper-layer copy.
  5. Later reads of /etc/app.conf find the upper copy first, so the changed version is visible in the container.

The lower file is never changed. Other containers using the same image continue to see the original contents.

A useful mental model is that the first modification changes where the file comes from. Before the write, reads are served from the lower layer. After copy-up, reads and writes are served from the upper layer.

Copy-on-write does not usually mean that only the changed bytes of a regular file are copied. The first write may require copying the entire file into the upper layer.

That detail matters for performance. Updating one byte in a large lower-layer file can cause a large copy-up, followed by the small write. Subsequent writes to that file do not repeatedly copy it: the upper copy already exists.

A read-only open does not normally require copy-up. Operations that alter file state can, however, require it even when they do not change the file’s contents. Examples include truncating a file, changing its permissions, changing ownership, or updating some timestamps. The exact behavior depends on the operation and filesystem implementation, but the rule is simple: a change that cannot be applied to the lower object must be represented in the upper layer.

New files, deleted files, and directories

A file created by the container has no lower version to preserve, so it is created directly in the upper layer. It is writable from the beginning.

Deletion needs special handling. Removing a lower-layer file cannot erase that file from the immutable layer. Instead, OverlayFS records a whiteout in the upper layer: a marker meaning “hide the lower entry with this name.” The merged view therefore omits the file even though it still physically exists below.

Directories can also be copied up when their metadata changes. Creating a new file inside a lower-layer directory may require OverlayFS to represent enough of that directory in the upper layer to record the new child. Directory operations such as renaming, changing permissions, or making a directory opaque can involve additional metadata and work files.

The result is not a second complete image. The upper layer contains only new files and the changes needed to hide or replace lower-layer entries. A container runtime can later treat that upper-layer content as the container’s writable diff, although the precise export or commit behavior belongs to the runtime rather than to the basic overlay mechanism.

Where the performance cost appears

Overlay filesystems make image sharing cheap, but they move some cost to the first mutation:

This explains log messages and incident reports mentioning copy-up, OverlayFS, or unexpectedly high write latency during startup. An application that rewrites a large configuration, database, or dependency file inside the container may pay the copy-up cost before its apparently small update completes.

The central trade-off is therefore straightforward: immutable lower layers are shared efficiently, while each container gets an independent writable view. Copy-up is the operation that preserves both properties when a process tries to change data supplied by the image.

← All articles