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.
- LowerDir (Read-Only): These are your image layers (Base OS, App Code, Libs). They are immutable.
- UpperDir (Read-Write): This is the container’s writable layer. Initially empty.
- 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...
3. Deep Dive: Inodes and Hard Links
Because Overlay2 is a Union Mount:
- Inodes: Files in
MergedDirshare the same inode as the file inLowerDiruntil they are modified. - 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.");
}
}
}