OverlayFS - Layer-based Filesystems for Raspberry Pi
Introduction
OverlayFS is a union filesystem that combines multiple directories into a single merged view, creating a powerful layered storage system. It's the technology behind Docker's storage driver and enables sophisticated file management strategies that are particularly valuable for Raspberry Pi's SD card-based storage.
This technology transforms how you manage filesystems:
- Layered Storage: Combine read-only base with writable overlay
- Non-Destructive Changes: Modify files without touching originals
- System Protection: Create immutable base systems with temporary changes
- Space Efficiency: Share common data across multiple instances
- Fast Snapshots: Instant filesystem branching and rollback
- A/B Updates: Safe system updates with fallback capability
Why OverlayFS matters for Raspberry Pi:
- SD Card Longevity: Reduce write cycles by using tmpfs overlays
- System Recovery: Always have a clean base system to fall back to
- Development Safety: Experiment freely without breaking your system
- Kiosk Mode: Public displays that reset on reboot
- Container Understanding: Learn Docker's storage internals
- Update Safety: Test updates without risking current system
Common use cases:
- Read-Only Root: Protect SD card and system integrity
- Temporary Workspaces: Changes disappear on reboot
- System Snapshots: Instant rollback capability
- A/B Partitioning: Safe over-the-air updates
- Multi-Instance: Run same base with different overlays
- Development: Test changes without affecting base
- Education: Students can experiment safely
- IoT Devices: Field devices with recovery capability
This comprehensive guide covers:
- OverlayFS Fundamentals: Lower, upper, work, and merged directories
- Basic Usage: Manual overlay mounting and management
- Read-Only Root: Complete SD card protection setup
- Tmpfs Overlays: Volatile changes for kiosk mode
- A/B Updates: Dual partition update system
- Docker Integration: Understanding container storage
- Boot Configuration: Automatic overlay on startup
- Performance: Optimization for Raspberry Pi
- Troubleshooting: Common issues and solutions
- Production Examples: Real-world deployments
Perfect for:
- System Administrators: Immutable infrastructure patterns
- IoT Developers: Field device management and updates
- DevOps Engineers: Understanding container storage layers
- Educators: Safe learning environments
- Kiosk Operators: Self-resetting public displays
- Embedded Engineers: Robust update mechanisms
- Home Automation: Stable, recoverable systems
- Security Professionals: Tamper-resistant deployments
Understanding OverlayFS
Architecture
| ┌─────────────────────────────────────────────────────────┐
│ Merged Directory │
│ (User's View) │
│ │
│ /merged/file1.txt ← Shows version from upperdir │
│ /merged/file2.txt ← Shows version from lowerdir │
│ /merged/file3.txt ← Created in upperdir │
│ │
└────────────┬────────────────────────────────────────────┘
│ OverlayFS combines layers
│
┌────────┴────────┐
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│UpperDir │ │ LowerDir │
│(Writable)│ │(Read-Only│
│ │ │ or R/W) │
│ Stores: │ │ │
│ - New │ │ Base │
│ files │ │ system │
│ - Mods │ │ files │
│ - Deletes│ │ │
│ (whiteouts)│ │
└──────────┘ └──────────┘
│
│ Can be:
│ - Real partition
│ - Another overlay
│ - Multiple layers
│
┌──────────┴──────────┐
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ Layer 2 │ │ Layer 3 │
└─────────┘ └─────────┘
┌──────────────────────────────────────────────────────────┐
│ Work Directory │
│ (Internal OverlayFS Use) │
│ │
│ Used for atomic operations and copy-up │
│ Must be on same filesystem as upperdir │
│ Not directly accessed by users │
└──────────────────────────────────────────────────────────┘
|
How OverlayFS Works
File Operations:
| Read Operation:
1. Check upperdir first
2. If not found, check lowerdir
3. Return first match
Write Operation (file exists in lowerdir):
1. Copy file to upperdir (copy-up)
2. Modify copy in upperdir
3. Lowerdir remains unchanged
Write Operation (new file):
1. Create directly in upperdir
2. Visible in merged view
Delete Operation:
1. Create "whiteout" file in upperdir
2. Hides file from lowerdir
3. Lowerdir file remains (hidden)
|
Directory Structure Example
| # Example layout
/lower/ # Read-only base
├── file1.txt # Original: "Hello"
├── file2.txt # Original: "World"
└── dir/
└── file3.txt
/upper/ # Writable overlay
├── file1.txt # Modified: "Hello Modified!"
├── file4.txt # New file
└── dir/
└── file3.txt # (c 0 0) - whiteout (deleted)
/work/ # OverlayFS working directory
└── (internal)
/merged/ # Combined view (what user sees)
├── file1.txt # From upper: "Hello Modified!"
├── file2.txt # From lower: "World"
├── file4.txt # From upper: New file
└── dir/ # Empty (file3.txt whited out)
|
Basic OverlayFS Usage
| # OverlayFS is built into kernel (no installation needed)
# Verify kernel support
grep -i overlay /proc/filesystems
# Should show: overlay
# Install utilities for testing
sudo apt update
sudo apt install -y fuse-overlayfs # Userspace overlay (optional)
|
Simple Overlay Example
| # Create directory structure
mkdir -p /tmp/overlay-test/{lower,upper,work,merged}
# Create base files in lower layer
echo "Original file 1" > /tmp/overlay-test/lower/file1.txt
echo "Original file 2" > /tmp/overlay-test/lower/file2.txt
echo "Read-only data" > /tmp/overlay-test/lower/readonly.txt
# Mount overlay
sudo mount -t overlay overlay \
-o lowerdir=/tmp/overlay-test/lower,\
upperdir=/tmp/overlay-test/upper,\
workdir=/tmp/overlay-test/work \
/tmp/overlay-test/merged
# Verify mount
mount | grep overlay
# View merged content
ls -la /tmp/overlay-test/merged/
# Shows all files from lower layer
# Modify a file
echo "Modified in overlay" > /tmp/overlay-test/merged/file1.txt
# Create new file
echo "New file" > /tmp/overlay-test/merged/newfile.txt
# Delete a file (creates whiteout)
rm /tmp/overlay-test/merged/readonly.txt
# Check results
echo "=== Merged view ==="
ls -la /tmp/overlay-test/merged/
echo "=== Lower layer (unchanged) ==="
ls -la /tmp/overlay-test/lower/
cat /tmp/overlay-test/lower/file1.txt # Still "Original file 1"
echo "=== Upper layer (changes) ==="
ls -la /tmp/overlay-test/upper/
cat /tmp/overlay-test/upper/file1.txt # "Modified in overlay"
# Unmount
sudo umount /tmp/overlay-test/merged
|
Multiple Lower Layers
| # OverlayFS supports multiple lower layers (stacked)
mkdir -p /tmp/multi/{layer1,layer2,layer3,upper,work,merged}
# Create files in each layer
echo "From layer 1" > /tmp/multi/layer1/file1.txt
echo "From layer 2" > /tmp/multi/layer2/file2.txt
echo "From layer 3" > /tmp/multi/layer3/file3.txt
echo "Overridden" > /tmp/multi/layer2/file1.txt # Overrides layer1
# Mount with multiple lower layers (rightmost is lowest priority)
sudo mount -t overlay overlay \
-o lowerdir=/tmp/multi/layer1:/tmp/multi/layer2:/tmp/multi/layer3,\
upperdir=/tmp/multi/upper,\
workdir=/tmp/multi/work \
/tmp/multi/merged
# layer1 has highest priority, layer3 lowest
# If same file exists in multiple layers, highest priority wins
ls /tmp/multi/merged/
cat /tmp/multi/merged/file1.txt # "Overridden" (from layer2)
sudo umount /tmp/multi/merged
|
Read-Only Root Filesystem
Benefits for Raspberry Pi
- SD Card Longevity: Dramatically reduces write cycles
- System Stability: Prevents corruption from power loss
- Security: Harder to tamper with system files
- Easy Recovery: Reboot to clean state
- Predictability: System always starts the same way
Creating Read-Only Root with Tmpfs Overlay
| #!/bin/bash
# setup-ro-root.sh - Configure read-only root with tmpfs overlay
# Step 1: Edit /boot/firmware/cmdline.txt
# Add to kernel parameters:
# boot=overlay overlayroot=tmpfs
# Step 2: Install overlayroot
sudo apt update
sudo apt install -y overlayroot
# Step 3: Configure overlayroot
sudo tee /etc/overlayroot.conf <<EOF
overlayroot="tmpfs"
overlayroot_cfgdisk="disabled"
EOF
# Step 4: Edit /etc/fstab - make root read-only
sudo cp /etc/fstab /etc/fstab.backup
sudo sed -i 's/defaults/defaults,ro/g' /etc/fstab
# Step 5: Reboot
echo "Configuration complete. System will use tmpfs overlay after reboot."
echo "All changes will be lost on reboot unless saved to lower layer."
echo "Reboot now? (y/n)"
read -r REPLY
if [[ $REPLY =~ ^[Yy]$ ]]; then
sudo reboot
fi
|
Manual Read-Only Root Setup
| #!/bin/bash
# manual-ro-root.sh - Manual read-only root configuration
# Create overlay directories
sudo mkdir -p /overlay/upper
sudo mkdir -p /overlay/work
# Add to /etc/initramfs-tools/scripts/init-bottom/overlay
sudo tee /etc/initramfs-tools/scripts/init-bottom/overlay <<'EOF'
#!/bin/sh
PREREQ=""
prereqs()
{
echo "$PREREQ"
}
case $1 in
prereqs)
prereqs
exit 0
;;
esac
. /scripts/functions
# Mount tmpfs for overlay
mount -t tmpfs tmpfs /overlay
mkdir -p /overlay/upper /overlay/work
# Create overlay on root
mount -t overlay overlay \
-o lowerdir=/root,upperdir=/overlay/upper,workdir=/overlay/work \
/root
exit 0
EOF
# Make executable
sudo chmod +x /etc/initramfs-tools/scripts/init-bottom/overlay
# Update initramfs
sudo update-initramfs -u
# Modify /etc/fstab
sudo sed -i 's/\(.*\/.*ext4.*defaults\)/\1,ro/' /etc/fstab
echo "Read-only root configured. Reboot to activate."
|
Persistent Storage Areas
| # Even with read-only root, some directories need to be writable
# /etc/fstab - add tmpfs mounts for writable areas
tmpfs /tmp tmpfs defaults,noatime,nosuid,size=100M 0 0
tmpfs /var/tmp tmpfs defaults,noatime,nosuid,size=50M 0 0
tmpfs /var/log tmpfs defaults,noatime,nosuid,size=50M 0 0
tmpfs /var/cache tmpfs defaults,noatime,nosuid,size=50M 0 0
# For persistent data (survives reboot)
# Use separate partition or USB storage
/dev/sda1 /persistent ext4 defaults,noatime 0 2
|
Making Permanent Changes to Read-Only System
| # Method 1: Remount root as read-write
sudo mount -o remount,rw /
# Make changes
sudo apt update
sudo apt install -y some-package
# Remount as read-only
sudo mount -o remount,ro /
# Method 2: Use overlayroot-chroot (if using overlayroot package)
sudo overlayroot-chroot
# Now in chroot environment with read-write access
apt update && apt install some-package
exit
# Reboot to apply changes
sudo reboot
|
Kiosk Mode with Auto-Reset
| #!/bin/bash
# kiosk-setup.sh - Self-resetting kiosk configuration
# Create overlay script
sudo tee /usr/local/bin/kiosk-overlay.sh <<'EOF'
#!/bin/bash
# Mount tmpfs for overlay storage
mount -t tmpfs tmpfs /overlay
mkdir -p /overlay/upper /overlay/work
# Mount overlay on home directory
mount -t overlay overlay \
-o lowerdir=/home/kiosk-base,upperdir=/overlay/upper,workdir=/overlay/work \
/home/kiosk
# All changes to /home/kiosk are now volatile
EOF
chmod +x /usr/local/bin/kiosk-overlay.sh
# Create systemd service
sudo tee /etc/systemd/system/kiosk-overlay.service <<EOF
[Unit]
Description=Kiosk Overlay Mount
DefaultDependencies=no
After=local-fs.target
Before=chromium.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/kiosk-overlay.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
# Enable service
sudo systemctl daemon-reload
sudo systemctl enable kiosk-overlay.service
echo "Kiosk overlay configured. Changes reset on reboot."
|
A/B Partition Updates
Dual Root Partition Setup
| Disk Layout:
┌────────────────────────────────────────────┐
│ /dev/mmcblk0 │
├────────────────────────────────────────────┤
│ /dev/mmcblk0p1 - /boot (shared) │
│ 256MB - FAT32 │
├────────────────────────────────────────────┤
│ /dev/mmcblk0p2 - Root A (primary) │
│ 4GB - ext4 │
├────────────────────────────────────────────┤
│ /dev/mmcblk0p3 - Root B (backup) │
│ 4GB - ext4 │
├────────────────────────────────────────────┤
│ /dev/mmcblk0p4 - Data (persistent) │
│ Remaining space - ext4 │
└────────────────────────────────────────────┘
|
A/B Update Script
| #!/bin/bash
# ab-update.sh - A/B partition update system
set -e
# Configuration
BOOT_PARTITION="/dev/mmcblk0p1"
ROOT_A="/dev/mmcblk0p2"
ROOT_B="/dev/mmcblk0p3"
MOUNT_A="/mnt/root-a"
MOUNT_B="/mnt/root-b"
# Determine current root
CURRENT_ROOT=$(findmnt -n -o SOURCE /)
if [ "$CURRENT_ROOT" = "$ROOT_A" ]; then
CURRENT="A"
INACTIVE_ROOT="$ROOT_B"
INACTIVE_MOUNT="$MOUNT_B"
else
CURRENT="B"
INACTIVE_ROOT="$ROOT_A"
INACTIVE_MOUNT="$MOUNT_A"
fi
echo "Current root: Partition $CURRENT ($CURRENT_ROOT)"
echo "Inactive root: $INACTIVE_ROOT"
# Function to apply update
apply_update() {
local UPDATE_IMAGE=$1
echo "Mounting inactive partition..."
mkdir -p "$INACTIVE_MOUNT"
mount "$INACTIVE_ROOT" "$INACTIVE_MOUNT"
echo "Backing up current partition to inactive..."
rsync -aHAXxv --delete \
--exclude='/dev/*' \
--exclude='/proc/*' \
--exclude='/sys/*' \
--exclude='/tmp/*' \
--exclude='/run/*' \
--exclude='/mnt/*' \
--exclude='/media/*' \
/ "$INACTIVE_MOUNT/"
echo "Applying update to inactive partition..."
# Extract update or apply changes
if [ -f "$UPDATE_IMAGE" ]; then
tar -xzf "$UPDATE_IMAGE" -C "$INACTIVE_MOUNT/"
fi
echo "Update applied to inactive partition"
# Update boot configuration
update_boot_config
umount "$INACTIVE_MOUNT"
echo "Update complete. Reboot to activate."
echo "If system fails to boot, it will automatically fall back to current partition."
}
# Update boot configuration to use new root
update_boot_config() {
mount "$BOOT_PARTITION" /boot/firmware
# Update cmdline.txt
if [ "$CURRENT" = "A" ]; then
NEW_ROOT="$ROOT_B"
else
NEW_ROOT="$ROOT_A"
fi
sed -i "s|root=[^ ]*|root=$NEW_ROOT|" /boot/firmware/cmdline.txt
umount /boot/firmware
echo "Boot configuration updated to use $NEW_ROOT"
}
# Rollback to previous partition
rollback() {
echo "Rolling back to previous partition..."
update_boot_config
echo "Rollback configured. Reboot to activate."
}
# Main
case "${1:-}" in
update)
if [ -z "${2:-}" ]; then
echo "Usage: $0 update <update-image.tar.gz>"
exit 1
fi
apply_update "$2"
;;
rollback)
rollback
;;
status)
echo "Current partition: $CURRENT"
echo "Active root: $CURRENT_ROOT"
echo "Inactive root: $INACTIVE_ROOT"
;;
*)
echo "Usage: $0 {update|rollback|status} [update-image]"
exit 1
;;
esac
|
Automatic Boot Fallback
| # Add to /boot/firmware/config.txt for boot counting
boot_delay=1
# Create boot check script
sudo tee /usr/local/bin/boot-success.sh <<'EOF'
#!/bin/bash
# Mark current boot as successful
BOOT_COUNT_FILE="/boot/firmware/boot_count.txt"
if [ -f "$BOOT_COUNT_FILE" ]; then
rm "$BOOT_COUNT_FILE"
echo "Boot successful, counter reset"
fi
EOF
chmod +x /usr/local/bin/boot-success.sh
# Create systemd service to mark boot as successful
sudo tee /etc/systemd/system/boot-success.service <<EOF
[Unit]
Description=Mark Boot as Successful
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/boot-success.sh
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable boot-success.service
|
Docker and OverlayFS
Understanding Docker's Use of OverlayFS
| # Docker uses overlay2 storage driver by default
# Check Docker's storage driver
docker info | grep "Storage Driver"
# Output: Storage Driver: overlay2
# View overlay mounts for containers
docker run -d --name test-container nginx
mount | grep overlay | grep test-container
# Example output:
# overlay on /var/lib/docker/overlay2/abc123.../merged type overlay
# (lowerdir=...image-layers...,upperdir=...container-layer...,workdir=...)
# Inspect container's layers
docker inspect test-container | grep -A 10 GraphDriver
# Output shows:
# - LowerDir: Image layers (read-only)
# - UpperDir: Container layer (writable)
# - WorkDir: OverlayFS working directory
# - MergedDir: Combined view inside container
docker rm -f test-container
|
Docker Image Layers Visualization
| Docker Image: nginx:latest
┌─────────────────────────────────────────┐
│ Container Layer (UpperDir) │ ← Read/Write
│ - Application logs │
│ - Temporary files │
│ - Runtime changes │
├─────────────────────────────────────────┤
│ nginx:latest layer │ ← Read-only
│ - nginx binaries │
│ - nginx config │
├─────────────────────────────────────────┤
│ debian:bullseye layer │ ← Read-only
│ - Base OS files │
│ - System libraries │
└─────────────────────────────────────────┘
Each layer is a separate directory in:
/var/lib/docker/overlay2/
When you run a container:
- All image layers become lowerdir (read-only)
- New container layer becomes upperdir (writable)
- Changes don't affect image (can spawn unlimited containers)
|
Manual Container-Like Overlay
| #!/bin/bash
# container-like-overlay.sh - Simulate Docker's overlay approach
# Create directory structure
BASE_DIR="/tmp/container-sim"
mkdir -p "$BASE_DIR"/{image-layers,containers}
# Create "image layers"
mkdir -p "$BASE_DIR/image-layers/layer1"
echo "#!/bin/bash" > "$BASE_DIR/image-layers/layer1/app.sh"
echo "echo 'Hello from app'" >> "$BASE_DIR/image-layers/layer1/app.sh"
chmod +x "$BASE_DIR/image-layers/layer1/app.sh"
mkdir -p "$BASE_DIR/image-layers/layer2"
echo "config=default" > "$BASE_DIR/image-layers/layer2/config.txt"
# Function to create container instance
create_container() {
CONTAINER_ID="container-$$-$(date +%s)"
CONTAINER_DIR="$BASE_DIR/containers/$CONTAINER_ID"
mkdir -p "$CONTAINER_DIR"/{upper,work,merged}
# Mount overlay
sudo mount -t overlay overlay \
-o lowerdir="$BASE_DIR/image-layers/layer2:$BASE_DIR/image-layers/layer1",\
upperdir="$CONTAINER_DIR/upper",\
workdir="$CONTAINER_DIR/work" \
"$CONTAINER_DIR/merged"
echo "Container created: $CONTAINER_ID"
echo "Root directory: $CONTAINER_DIR/merged"
echo "Run: sudo chroot $CONTAINER_DIR/merged /bin/bash"
echo "Stop: sudo umount $CONTAINER_DIR/merged"
return 0
}
# Create multiple containers from same base
create_container
sleep 1
create_container
# Both containers share the same image layers (lowerdir)
# But have independent writable layers (upperdir)
# Changes in one container don't affect the other
|
| # Performance comparison: Direct FS vs OverlayFS
# Test setup
mkdir -p /tmp/perf-test/{direct,overlay/{lower,upper,work,merged}}
# Direct filesystem test
time dd if=/dev/zero of=/tmp/perf-test/direct/testfile bs=1M count=100
# Baseline write performance
# OverlayFS test
sudo mount -t overlay overlay \
-o lowerdir=/tmp/perf-test/overlay/lower,\
upperdir=/tmp/perf-test/overlay/upper,\
workdir=/tmp/perf-test/overlay/work \
/tmp/perf-test/overlay/merged
time dd if=/dev/zero of=/tmp/perf-test/overlay/merged/testfile bs=1M count=100
# OverlayFS write performance (new file - similar to direct)
# Copy-up test (modifying file from lowerdir)
dd if=/dev/zero of=/tmp/perf-test/overlay/lower/existing bs=1M count=100
sudo umount /tmp/perf-test/overlay/merged
sudo mount -t overlay overlay \
-o lowerdir=/tmp/perf-test/overlay/lower,\
upperdir=/tmp/perf-test/overlay/upper,\
workdir=/tmp/perf-test/overlay/work \
/tmp/perf-test/overlay/merged
time dd if=/dev/zero of=/tmp/perf-test/overlay/merged/existing bs=1M count=10 conv=notrunc
# First write triggers copy-up (slower)
sudo umount /tmp/perf-test/overlay/merged
|
Optimization Tips
| # 1. Keep frequently modified files in upperdir
# - Avoid copy-up overhead
# - Place application data in separate mount
# 2. Use tmpfs for upperdir when changes are temporary
mount -t tmpfs tmpfs /tmp/overlay-upper
mount -t overlay overlay \
-o lowerdir=/readonly-base,upperdir=/tmp/overlay-upper,workdir=/tmp/overlay-work \
/merged
# 3. Minimize layers
# - Fewer layers = faster lookups
# - Combine related files in same layer
# 4. Use redirect_dir option for better rename performance
mount -t overlay overlay \
-o lowerdir=/lower,upperdir=/upper,workdir=/work,redirect_dir=on \
/merged
# 5. Enable metacopy for large file handling
mount -t overlay overlay \
-o lowerdir=/lower,upperdir=/upper,workdir=/work,metacopy=on \
/merged
# Only metadata is copied up initially, data copied on actual modification
|
Troubleshooting
Common Issues
| # Issue 1: "workdir and upperdir must reside under the same mount"
# Solution: Ensure upperdir and workdir are on same filesystem
# Wrong:
# upperdir=/mnt/usb/upper (on USB)
# workdir=/tmp/work (on tmpfs)
# Correct:
mkdir -p /mnt/usb/{upper,work}
# Both on same mount point
# Issue 2: "filesystem is read-only"
# Check if underlying filesystem is read-only
mount | grep $(df /upper | tail -1 | awk '{print $1}')
# Remount as read-write
sudo mount -o remount,rw /underlying-mount
# Issue 3: Whiteout files visible
ls -la /upper/
# Shows: file (c 0 0) - character device 0:0
# This is normal - it's how OverlayFS tracks deletions
# They won't appear in merged view
# Issue 4: "too many levels of nesting"
# OverlayFS limits nesting depth
# Don't create overlay of overlay of overlay...
# Maximum nesting: 2 levels
# Issue 5: Permission denied on files
# OverlayFS requires proper permissions on all layers
chmod -R 755 /lower /upper /work
|
Debugging OverlayFS
| # View all overlay mounts
mount | grep overlay
# Detailed mount options
findmnt -t overlay
# Check which files are in which layer
# Lower layer only:
ls /lower/
# Upper layer only:
ls /upper/
# Merged view:
ls /merged/
# Find files that have been copied up
find /upper/ -type f
# Check for whiteouts (deleted files)
find /upper/ -type c
# Monitor overlay operations (requires kernel debug)
sudo trace-cmd record -e overlay
# Perform operations
sudo trace-cmd report
|
Real-World Examples
IoT Device with Recovery
| #!/bin/bash
# iot-device-setup.sh - Robust IoT device configuration
# Partition layout:
# p1: /boot
# p2: /rootfs-base (read-only system image)
# p3: /data (persistent application data)
# Create mount points
sudo mkdir -p /mnt/{rootfs-base,data,overlay/{upper,work}}
# /etc/fstab
cat <<EOF | sudo tee -a /etc/fstab
/dev/mmcblk0p2 /mnt/rootfs-base ext4 ro,noatime 0 2
/dev/mmcblk0p3 /mnt/data ext4 defaults,noatime 0 2
tmpfs /mnt/overlay/upper tmpfs size=256M 0 0
tmpfs /mnt/overlay/work tmpfs size=64M 0 0
EOF
# Create overlay mount service
sudo tee /etc/systemd/system/rootfs-overlay.service <<'EOF'
[Unit]
Description=Root Filesystem Overlay
DefaultDependencies=no
After=local-fs.target
Before=systemd-remount-fs.service
[Service]
Type=oneshot
ExecStart=/bin/mount -t overlay overlay \
-o lowerdir=/mnt/rootfs-base,upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work \
/
RemainAfterExit=yes
[Install]
WantedBy=local-fs.target
EOF
sudo systemctl enable rootfs-overlay.service
echo "IoT device configured with:"
echo " - Read-only base system (recovery image)"
echo " - Tmpfs overlay (changes lost on reboot)"
echo " - Persistent data partition (/mnt/data)"
|
Development Environment with Snapshots
| #!/bin/bash
# dev-snapshot.sh - Development environment with instant rollback
SNAPSHOT_DIR="/snapshots"
PROJECT_BASE="/projects/myapp-base"
WORK_DIR="/workspace/myapp"
mkdir -p "$SNAPSHOT_DIR" "$WORK_DIR"
# Function to create snapshot
create_snapshot() {
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
SNAP_NAME="snapshot-$TIMESTAMP"
mkdir -p "$SNAPSHOT_DIR/$SNAP_NAME"
# Copy current overlay upper layer
if [ -d "/tmp/overlay-upper" ]; then
cp -a /tmp/overlay-upper "$SNAPSHOT_DIR/$SNAP_NAME/upper"
echo "Snapshot created: $SNAP_NAME"
else
echo "No overlay mounted"
return 1
fi
}
# Function to restore snapshot
restore_snapshot() {
SNAP_NAME=$1
if [ ! -d "$SNAPSHOT_DIR/$SNAP_NAME" ]; then
echo "Snapshot not found: $SNAP_NAME"
return 1
fi
# Unmount current overlay
sudo umount "$WORK_DIR" 2>/dev/null
# Clear current overlay
rm -rf /tmp/overlay-upper/*
rm -rf /tmp/overlay-work/*
# Restore snapshot
cp -a "$SNAPSHOT_DIR/$SNAP_NAME/upper/"* /tmp/overlay-upper/
# Remount overlay
mount_workspace
echo "Restored snapshot: $SNAP_NAME"
}
# Mount workspace with overlay
mount_workspace() {
mkdir -p /tmp/{overlay-upper,overlay-work}
sudo mount -t overlay overlay \
-o lowerdir="$PROJECT_BASE",upperdir=/tmp/overlay-upper,workdir=/tmp/overlay-work \
"$WORK_DIR"
echo "Workspace mounted at $WORK_DIR"
}
# List snapshots
list_snapshots() {
echo "Available snapshots:"
ls -1 "$SNAPSHOT_DIR" | grep "^snapshot-"
}
# Main
case "${1:-}" in
mount)
mount_workspace
;;
snapshot)
create_snapshot
;;
restore)
if [ -z "${2:-}" ]; then
echo "Usage: $0 restore <snapshot-name>"
list_snapshots
exit 1
fi
restore_snapshot "$2"
;;
list)
list_snapshots
;;
*)
echo "Usage: $0 {mount|snapshot|restore|list} [snapshot-name]"
exit 1
;;
esac
|
Live System Testing
| #!/bin/bash
# test-overlay.sh - Test system changes without risk
echo "Creating test overlay environment..."
# Create overlay directories in tmpfs
mkdir -p /tmp/test-overlay/{upper,work}
# Mount overlay on root
sudo mount -t overlay overlay \
-o lowerdir=/,upperdir=/tmp/test-overlay/upper,workdir=/tmp/test-overlay/work \
/mnt/test-root
echo "Test environment ready at /mnt/test-root"
echo ""
echo "You can now:"
echo " 1. Chroot into test environment: sudo chroot /mnt/test-root"
echo " 2. Make any changes (install packages, modify configs, etc.)"
echo " 3. Test your changes"
echo " 4. Exit and unmount - all changes are discarded"
echo ""
echo "Your real system remains untouched."
echo ""
echo "To enter test environment:"
echo " sudo chroot /mnt/test-root /bin/bash"
echo ""
echo "To cleanup when done:"
echo " exit # (from chroot)"
echo " sudo umount /mnt/test-root"
|
Advanced Techniques
Layered Configuration Management
| #!/bin/bash
# config-layers.sh - Manage configurations in layers
# Layer structure:
# - Base layer: Default configurations
# - Environment layer: Dev/Staging/Prod specific
# - Local layer: Machine-specific overrides
mkdir -p /etc/myapp/{base,env-prod,env-dev,local,merged}
# Base configuration
cat > /etc/myapp/base/config.ini <<EOF
[app]
name=MyApp
debug=false
port=8080
EOF
# Production overlay
cat > /etc/myapp/env-prod/config.ini <<EOF
[app]
debug=false
database=prod-db.example.com
EOF
# Development overlay
cat > /etc/myapp/env-dev/config.ini <<EOF
[app]
debug=true
database=localhost
EOF
# Mount appropriate environment
ENV=${1:-prod}
sudo mount -t overlay overlay \
-o lowerdir="/etc/myapp/env-$ENV:/etc/myapp/base",\
upperdir=/etc/myapp/local,\
workdir=/tmp/myapp-work \
/etc/myapp/merged
echo "Configuration merged for environment: $ENV"
echo "Merged config:"
cat /etc/myapp/merged/config.ini
|
Temporary Root for Updates
| #!/bin/bash
# safe-update.sh - Update system in temporary overlay
echo "Creating temporary overlay for safe updates..."
# Mount tmpfs overlay on root
mkdir -p /tmp/update-overlay/{upper,work}
sudo mount -t overlay overlay \
-o lowerdir=/,upperdir=/tmp/update-overlay/upper,workdir=/tmp/update-overlay/work \
/mnt/update-root
echo "Entering update environment..."
sudo chroot /mnt/update-root /bin/bash -c '
echo "You are now in temporary update environment"
echo "Install updates, test, and exit when satisfied"
echo ""
# Example: Update system
apt update
apt list --upgradable
echo ""
echo "To apply updates: apt upgrade"
echo "To test: run your applications"
echo "If satisfied: exit and run commit-updates.sh"
echo "If not: just exit (changes discarded)"
bash
'
echo ""
echo "Exited update environment"
echo "Changes are in /tmp/update-overlay/upper"
echo ""
echo "To commit changes: sudo rsync -av /tmp/update-overlay/upper/ /"
echo "To discard: sudo rm -rf /tmp/update-overlay && sudo umount /mnt/update-root"
|
Summary
This comprehensive guide covered OverlayFS for Raspberry Pi:
✅ Core Concepts
- Layer-based filesystem architecture
- LowerDir, UpperDir, WorkDir, and MergedDir
- Copy-up operations and whiteout files
- Multiple lower layer stacking
✅ Basic Usage
- Manual overlay mounting and management
- Simple overlay examples
- Multiple layer configurations
- File operation behavior
✅ Read-Only Root
- SD card protection through read-only base
- Tmpfs overlays for volatile changes
- Persistent storage areas configuration
- Making permanent changes safely
✅ Kiosk Mode
- Self-resetting public displays
- Automatic reset on reboot
- Volatile home directories
- User session isolation
✅ A/B Updates
- Dual partition setup
- Safe over-the-air updates
- Automatic fallback on boot failure
- Rollback capability
✅ Docker Integration
- Understanding overlay2 storage driver
- Container image layers
- How Docker uses OverlayFS
- Manual container-like overlays
- Performance characteristics
- Optimization techniques
- Copy-up overhead mitigation
- Best practices for Raspberry Pi
✅ Troubleshooting
- Common issues and solutions
- Debugging techniques
- Permission problems
- Mount option errors
✅ Real-World Applications
- IoT device with recovery capability
- Development environment snapshots
- Live system testing without risk
- Configuration layer management
✅ Advanced Techniques
- Layered configuration management
- Temporary root for safe updates
- Multi-environment configurations
- Update commit workflows
Next Steps
Advanced Topics:
- AUFS/UnionFS: Alternative union filesystems
- Btrfs Snapshots: Filesystem-level snapshots and rollback
- LVM Snapshots: Block-level snapshot management
- Podman/Buildah: Rootless container tools using overlay
- OSTree: Immutable OS deployment system
Related Guides:
With OverlayFS, you can create robust, recoverable systems that protect your Raspberry Pi's SD card, enable safe experimentation, and provide enterprise-grade update capabilities. This is the same technology powering Docker containers and modern immutable infrastructure!