Overlay2: The Magic of Layers

Docker images are built from layers. But when you run a container, you see a single, unified filesystem. How does Docker merge 10+ read-only image layers into one writable view without copying gigabytes of data? The answer is Overlay2.

1. The Architecture of a Union Filesystem

Overlay2 uses a “Union Mount” to combine multiple directories into one.

  1. LowerDir (Read-Only): These are your image layers (Base OS, App Code, Libs). They are immutable.
  2. UpperDir (Read-Write): This is the container’s writable layer. Initially empty.
  3. MergedDir (Unified View): This is what the container sees.

Copy-on-Write (CoW)

This is the critical performance mechanism:

  • Read: If you read /etc/nginx.conf, Docker looks in UpperDir. If not found, it looks in LowerDir. This is fast (native speed).
  • Write (Modify): If you modify /etc/nginx.conf, Docker first copies the file from LowerDir to UpperDir.
  • This “copy up” operation takes time.
  • Once copied, all future writes happen to the copy in UpperDir.

[!TIP] Performance Hit: The first time you modify a large file (e.g., a 1GB log file from the image), it must be copied up entirely. This causes a write latency spike.


2. Interactive: Layer Stack Explorer

Visualize how files exist in different layers and how CoW moves them.

Merged View (Container Sees This)
📄 app.conf
📄 lib.so
⬇ Unions ⬇
UpperDir (Read-Write Layer)
Empty (Changes go here)
LowerDir (Read-Only Image)
📄 app.conf
📄 lib.so

Select an action to simulate:

Waiting for action...

Because Overlay2 is a Union Mount:

  1. Inodes: Files in MergedDir share the same inode as the file in LowerDir until they are modified.
  2. Hard Links: Breaking hard links is a known issue. If you hard link a file in the container, it triggers a copy-up, breaking the link to the original lower layer file.

How to Check Driver Status

You can programmatically inspect which storage driver your Docker daemon is using.

package main

import (
    "context"
    "fmt"
    "github.com/docker/docker/client"
)

func main() {
    ctx := context.Background()
    cli, err := client.NewClientWithOpts(client.FromEnv)
    if err != nil {
        panic(err)
    }

    info, err := cli.Info(ctx)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Storage Driver: %s\n", info.Driver)
    // Example Output: Storage Driver: overlay2

    // Inspect specific image layers
    image, _, _ := cli.ImageInspectWithRaw(ctx, "nginx:latest")
    fmt.Printf("GraphDriver: %v\n", image.GraphDriver.Data)
}
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.api.model.Info;

public class DriverCheck {
    public static void main(String[] args) {
        DockerClient dockerClient = DockerClientBuilder.getInstance().build();

        Info info = dockerClient.infoCmd().exec();
        System.out.println("Storage Driver: " + info.getDriver());

        // Check for Overlay2
        if ("overlay2".equals(info.getDriver())) {
            System.out.println("Optimized for production.");
        }
    }
}