OverlayFS & container storage
OverlayFS is a union filesystem that merges multiple directories into a single unified view. It is the foundation of modern container storage, enabling efficient layering where read-only base images are combined with writable container layers.
OverlayFS architecture
OverlayFS combines a lowerdir (read-only) and an upperdir (writable) into a merged view. A workdir is required for atomic operations when an upperdir is present.
| Component | Purpose |
|---|---|
| lowerdir | Read-only layer(s). Colon-separated stack, rightmost is bottom. |
| upperdir | Writable layer. All modifications stored here. Omit for read-only overlay. |
| workdir | Required with upperdir. Empty directory on same filesystem as upperdir. |
| merged | Mount point presenting the unified view. |
Basic mount
# Single lower layer with writable upper
mount -t overlay overlay \
-olowerdir=/lower,upperdir=/upper,workdir=/work \
/merged
# Multiple lower layers (read-only)
mount -t overlay overlay \
-olowerdir=/lower1:/lower2:/lower3 \
/merged
# Read-only overlay (no upperdir/workdir)
mount -t overlay overlay \
-olowerdir=/lower1:/lower2 \
/mergedfstab entry
overlay /merged overlay lowerdir=/lower,upperdir=/var/lib/overlay/upper,workdir=/var/lib/overlay/work 0 0Multiple lower layers
Multiple lower directories use colon separators. Stacking order is right-to-left: the rightmost directory is the bottom layer, the leftmost is the top (closest to upperdir).
Whiteouts and opaque directories
To support deletion without modifying the lower filesystem, OverlayFS uses whiteouts and opaque directories stored in the upper layer.
- Whiteout: marks a file as deleted. Created as a character device with 0/0 device number, or a zero-size regular file with the xattr
trusted.overlay.whiteout. - Opaque directory: hides the contents of the same-named directory in lower layers. Set with
trusted.overlay.opaquexattr to"y".
# Mark a directory as opaque
setfattr -n trusted.overlay.opaque -v "y" /upper/mydirCopy-up behavior
When a file from the lower layer is accessed in a way that requires write access (opening for write, changing metadata, creating a hardlink), OverlayFS performs a copy_up operation to copy the file to the upper layer.
- Ensure the containing directory exists in upperdir (create parents if needed)
- Create the object with the same metadata (owner, mode, mtime, symlink target)
- If a file, copy data from lower to upper (may use
clone_file_rangeif same filesystem) - Copy extended attributes from lower to upper
- For directories, mark as opaque if needed
OverlayFS mount options
| Option | Values | Description |
|---|---|---|
redirect_dir | on|off|follow|nofollow | Enable directory redirects for fast renames |
metacopy | on|off | Copy only metadata on chmod/chown, defer data copy |
index | on|off | Preserve hardlinks on copy-up (required for NFS export) |
xino | on|off|auto | Map inodes from different filesystems to unique values |
nfs_export | on|off | Enable NFS export support (requires index) |
userxattr | flag | Use user.overlay.* xattrs (for unprivileged mounts) |
volatile | flag | Skip consistency checks (for ephemeral containers) |
Since kernel 4.19, metacopy=on automatically enables redirect_dir=on. If both are specified with conflicting values, mount will fail.
Kernel configuration
| Config option | Description |
|---|---|
CONFIG_OVERLAY_FS | Base OverlayFS support |
CONFIG_OVERLAY_FS_REDIRECT_DIR | Enable redirect_dir by default |
CONFIG_OVERLAY_FS_INDEX | Enable index by default |
CONFIG_OVERLAY_FS_METACOPY | Enable metacopy by default (implies redirect_dir) |
CONFIG_OVERLAY_FS_XINO_AUTO | Enable xino=auto by default |
CONFIG_OVERLAY_FS_NFS_EXPORT | Enable NFS export by default (requires index, conflicts with metacopy) |
OverlayFS limitations
- Rename across layers: without
redirect_dir=on, renaming a directory from a lower layer fails withEXDEV.mvfalls back to recursive copy-delete. - Device nodes: limited support in lower layers. Container runtimes handle devices via cgroups, not OverlayFS.
- mmap consistency: if a lower file is memory-mapped and then copied up, the mapping may see stale data. Use
read()for security-sensitive scenarios. - Backing filesystem: upperdir must support xattrs and
d_typein readdir. XFS must be formatted withftype=1. NFS is not suitable as an upper filesystem.
Docker storage drivers
The overlay2 driver is the default and recommended driver on modern Linux systems. It uses the kernel's OverlayFS implementation and stores data in /var/lib/docker/overlay2/.
Directory structure
/var/lib/docker/overlay2/
├── l/ # Shortened layer identifiers (symlinks)
│ ├── ABCDEF... -> ../abcdef...
│ └── ...
├── abcdef.../ # Layer directory
│ ├── diff/ # Layer contents (actual files)
│ ├── link # Shortened identifier name
│ ├── lower # References to parent layers (colon-separated)
│ ├── merged/ # Unified view (mount point)
│ └── work/ # OverlayFS work directory
└── ...Checking storage driver
docker info
...
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Native Overlay Diff: true
Using metacopy: falsePrerequisites
- Linux kernel 4.0+ or RHEL/CentOS 3.10.0-514+
- XFS backing filesystem with
ftype=1(check withxfs_info) - ext4 is also supported
# Verify XFS ftype
xfs_info /var/lib/docker | grep ftype
# Format XFS with ftype=1 if needed
mkfs.xfs -n ftype=1 /dev/sdb1Configuring overlay2
# /etc/docker/daemon.json
{
"storage-driver": "overlay2"
}
sudo systemctl restart docker
docker info | grep "Storage Driver"Legacy storage drivers
| Driver | Status | Notes |
|---|---|---|
| devicemapper | Legacy | Was default on RHEL 7. Requires dedicated block device. Not recommended. |
| btrfs | Discouraged | Uses Btrfs subvolumes. Known issues. Only if you need Btrfs features. |
| zfs | Discouraged | Uses ZFS datasets. Only if you already use ZFS. |
| vfs | Testing only | No copy-on-write. Full copy per layer. High disk usage. |
Podman / containerd / CRI-O storage
Podman, containerd, and CRI-O use the containers/storage library. By default, data lives in /var/lib/containers/storage (root) or $HOME/.local/share/containers/storage (rootless).
# /etc/containers/storage.conf
[storage]
driver = "overlay"
runroot = "/run/containers/storage"
graphroot = "/var/lib/containers/storage"
[storage.options]
mount_program = "/usr/bin/fuse-overlayfs" # For rootless on older kernels
mountopt = "nodev"Checking storage driver
podman info -f '{{.Store.GraphDriverName}}'
podman info -f '{{index .Store.GraphStatus "Native Overlay Diff"}}'Rootless overlay support
Rootless containers can use native OverlayFS since kernel 5.13. Before that, or on older kernels, fuse-overlayfs is used as a fallback.
BuildKit and build cache
BuildKit is Docker's modern build engine. It caches build results to speed up subsequent builds.
| Backend | Description |
|---|---|
inline | Embeds cache metadata into the image config. Pushed with the image. |
registry | Stores cache in a separate image in the registry. |
local | Stores cache as files in a local directory (OCI image layout). |
gha | Uploads cache to GitHub Actions cache. |
# Registry cache
docker buildx build --push -t myimage:latest \
--cache-to type=registry,ref=myrepo/myimage:cache \
--cache-from type=registry,ref=myrepo/myimage:cache \
.
# Cache mode (min vs max)
# min: only cache layers for resulting image (default)
# max: cache all intermediate layers
docker buildx build --cache-to type=registry,ref=mycache,mode=max .Cache mounts
# Dockerfile with cache mount for package manager
RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && apt-get install -y python3Volumes, bind mounts, and tmpfs
| Type | What it is | Best for | Persists? |
|---|---|---|---|
| Named volume | Docker/Podman-managed directory | Databases, app state | Yes |
| Anonymous volume | Unnamed Docker-managed directory | Rarely used | Yes (unless --rm) |
| Bind mount | Host path mapped into container | Dev, config files | Yes |
| tmpfs | In-memory storage | Temp data, caches | No |
Named volumes
# Create and use
docker volume create mydata
docker run -v mydata:/data myimage
docker volume rm mydataBind mounts
# Bind mount a directory
docker run -v /host/path:/container/path myimage
# Read-only bind mount
docker run -v /host/path:/container/path:ro myimagetmpfs mounts
# tmpfs with size limit
docker run --tmpfs /tmp:rw,size=100m myimageKubernetes persistent volumes
Kubernetes provides persistent storage through PersistentVolumes (PV) and PersistentVolumeClaims (PVC). StorageClasses enable dynamic provisioning via CSI drivers.
PersistentVolume (PV)
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-example
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: fast
hostPath:
path: /dataPersistentVolumeClaim (PVC)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-example
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: fastStorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: csi.driver.example.com
parameters:
type: ssd
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
mountOptions:
- discardReclaim policies
| Policy | Description |
|---|---|
Retain | PV is not deleted when PVC is deleted. Manual cleanup required. |
Delete | PV and underlying storage are deleted when PVC is deleted (default for dynamic provisioning). |
Best practices
Storage driver selection by backing filesystem
| Backing FS | Recommended driver | Notes |
|---|---|---|
| ext4 | overlay2 | Default choice, widely compatible |
| xfs (ftype=1) | overlay2 | Verify ftype=1 with xfs_info |
| xfs (ftype=0) | Reformat or use vfs | ftype=0 is incompatible with overlay2 |
| btrfs | overlay2 (not btrfs driver) | btrfs driver has known issues |
| zfs | overlay2 (not zfs driver) | zfs driver adds complexity |
Disk space management
# Check disk usage
docker system df
podman system df
# Prune unused resources
docker system prune -a --volumes
podman system prune -a --volumes
# Prune specific types
docker image prune -a
docker volume prune
docker builder pruneOverlayFS and SELinux
SELinux labels are supported on OverlayFS (fixed in RHEL 7.3). Volume mounts require proper labeling:
# Automatically relabel volume mount (private label)
docker run -v /host/data:/data:z myimage
# Recursively relabel
docker run -v /host/data:/data:Z myimageProduction recommendations
- Use overlay2 on ext4 or xfs (ftype=1). It is the most tested and performant combination.
- Use named volumes for persistent data; avoid bind mounts for production databases.
- Enable SELinux/AppArmor. Do not disable security features to work around permission issues.
- Automate regular pruning of unused images and containers.
- Monitor disk usage and set up alerts for
/var/lib/dockerand/var/lib/containers. - Use BuildKit cache backends to export cache to a registry for CI/CD.
- Avoid writing to the container layer. Use volumes for write-heavy workloads.
- Use native overlayfs for rootless containers, preferring kernel 5.13+ to avoid
fuse-overlayfsoverhead.