maskvol creates writable development masks over read-only source volumes.
It is designed for workflows where containers need realistic production-like
data, but every development write must stay isolated until an operator reviews
and explicitly merges it.
The tool is a single Bash CLI built on Linux overlayfs. It works with NFS shares, local directories, Docker bind mounts, Docker volumes, and other mounted filesystems.
SOURCE -- read-only bind --> WORK_ROOT/<id>/lower --+
WORK_ROOT/<id>/upper --+--> overlayfs --> TARGET
WORK_ROOT/<id>/work --+
SOURCEis the original data directory.TARGETis the writable merged view used by containers or developers.WORK_ROOTstores mask-local state for one TARGET:lower,upper, andwork.WORK_ROOT/<id>/loweris a read-only bind mount ofSOURCE. It is the baseline view that overlayfs reads from.WORK_ROOT/<id>/upperstores every new file, modified file, copied-up file, and overlay delete marker created throughTARGET.WORK_ROOT/<id>/workis overlayfs scratch space required by the kernel. It must be on the same filesystem asupperand is not user data.- The original
SOURCEis read-only bound and is not written by overlay use. - Changes can be reviewed with
diff, exported withexport, or merged withmerge.
maskvol up creates the three overlay inputs under WORK_ROOT/<id> and mounts
the merged filesystem at TARGET. Containers and users interact only with
TARGET; overlayfs decides whether each operation reads from lower, writes
to upper, or uses work internally.
Operation through TARGET |
SOURCE |
WORK_ROOT/<id>/lower |
WORK_ROOT/<id>/upper |
WORK_ROOT/<id>/work |
Result seen at TARGET |
|---|---|---|---|---|---|
| Read an unchanged file | Provides the original file. | Read-only bind view of SOURCE; overlayfs reads from here. |
Not used for that path. | Not used. | Original file appears. |
| Create a new file | Unchanged. | No new file appears. | New file is written here. | May be used internally by overlayfs. | New file appears. |
| Modify an existing lower file | Unchanged. | Original file remains read-only. | File is first copied up, then modified here. | Used by overlayfs during copy-up/metadata operations. | Modified version shadows lower. |
| Modify a file already in upper | Unchanged. | Not used for that path. | Existing upper file is modified in place. | May be used internally. | Modified upper version appears. |
| Delete a lower file | Unchanged. | Original file still exists. | A whiteout/delete marker is written here. | May be used internally. | File disappears from merged view. |
| Delete an upper-only file | Unchanged. | Not used for that path. | Upper file is removed. | May be used internally. | File disappears. |
| Delete or replace a lower directory | Unchanged. | Original directory still exists. | Overlayfs may create whiteouts and/or opaque directory metadata here. | Used for directory metadata operations. | Lower directory entries are hidden or replaced. |
| Rename within the mask | Unchanged. | Original lower paths remain read-only. | Renamed/copied-up entries and whiteouts are stored here as needed. | Used by overlayfs for rename bookkeeping. | Rename appears in merged view. |
Run maskvol diff |
Read for comparison only. | Not modified. | Scanned for changed files and delete markers. | Not modified. | Prints the pending change plan. |
Run maskvol export |
Unchanged. | Not exported. | Additions/modifications are copied to OUT_DIR; delete markers become a manifest. |
Not exported. | Produces a change-only export. |
Run maskvol merge |
Receives additions/modifications from upper. |
Not modified by merge. | Used as the merge source. | Not used. | SOURCE now includes merged changes. |
Run maskvol merge --delete |
Also deletes paths represented by upper whiteouts/opaque metadata. | Not modified by merge. | Used as the source of delete intent. | Not used. | SOURCE reflects additions, modifications, and explicit deletes. |
Important details:
WORK_ROOT/<id>/loweris a mountpoint, not a copy ofSOURCE.WORK_ROOT/<id>/upperis the only user-meaningful change layer.WORK_ROOT/<id>/workis required by overlayfs and should not be inspected, copied, edited, exported, or backed up as user data.TARGETis a merged view. Do not usersync --deletefromTARGETback toSOURCE; usemaskvol merge --deleteso delete intent comes only from overlayfs markers inupper.
- Linux with
overlayfs bashmount,findmnt,stat,rsync,python3- Root privileges for commands that mount, unmount, export, or merge
Optional:
docker, for reporting containers that currently use a maskfuser, for reporting active local processes
maskvol is a small orchestration layer around established open source and
Linux system components. The project depends on these tools and communities:
| Dependency | Used for |
|---|---|
| Linux overlayfs | The kernel merged filesystem that provides lower, upper, work, and TARGET semantics. |
| GNU Bash | The CLI implementation language. |
| util-linux | System utilities such as mount, findmnt, and mountpoint. |
| rsync | Change preview, export, and merge copy operations. |
| Python | Portable parsing of overlayfs whiteouts and opaque-directory metadata. |
| Docker | Optional container usage reporting and a common runtime for masked volumes. |
These components were not created by this project. maskvol exists because
they provide reliable primitives that can be combined into a safer development
workflow.
From this repository:
sudo install -m 0755 maskvol /usr/local/bin/maskvolThen verify:
maskvol doctorCreate a mask:
sudo maskvol up /data/media /srv/masks/media-devRun a container against the masked volume:
docker run --rm -v /srv/masks/media-dev:/data:rw ubuntu:latest ls /dataReview changes:
maskvol diff /srv/masks/media-devPreview a merge:
sudo maskvol merge /srv/masks/media-dev --dry-runMerge additions and modifications:
sudo maskvol merge /srv/masks/media-devMerge deletions too:
sudo maskvol merge /srv/masks/media-dev --deleteExport only changed files:
sudo maskvol export /srv/masks/media-dev /tmp/media-dev-exportTake the mask down while keeping the upper layer:
sudo maskvol down /srv/masks/media-devRemove all local mask state:
sudo maskvol purge /srv/masks/media-devRead-only commands:
| Command | Purpose |
|---|---|
doctor |
Check kernel, overlayfs, Fuse, registry, configured source roots, and Docker availability. |
scan |
List mounted source candidates and registered mask counts. |
ls |
List all registered masks. |
containers |
List Docker containers and their mounted host sources. |
tree |
Show mounted source to TARGET mapping. |
info <TARGET> |
Show metadata for one mask. |
path <TARGET> |
Print TARGET only if it is registered and mounted. |
diff <TARGET> |
Show additions, modifications, and overlay delete markers. |
Root commands:
| Command | Purpose |
|---|---|
up <SOURCE> <TARGET> |
Create or remount a writable mask. |
down <TARGET> |
Unmount TARGET and lower bind; preserve upper. |
purge <TARGET> |
Unmount and remove local upper/work/registry state. |
test <TARGET> |
Verify writable copy-up and that SOURCE remains clean. |
export <TARGET> <OUT_DIR> |
Export only UPPER additions/modifications. |
merge <TARGET> --dry-run [--delete] |
Preview a merge plan. |
merge <TARGET> [--delete] |
Merge UPPER changes into SOURCE after confirmation. |
gc |
Take down masks whose SOURCE disappeared. |
Most read commands support --json. Use --quiet or -q to suppress progress
messages where useful.
maskvol merge always uses registered metadata. Operators pass only TARGET;
the tool resolves the corresponding SOURCE and UPPER from the registry.
By default, merge copies only additions and modifications from UPPER to
SOURCE:
sudo maskvol merge /srv/masks/media-dev --dry-run
sudo maskvol merge /srv/masks/media-devDeletion support is explicit:
sudo maskvol merge /srv/masks/media-dev --dry-run --delete
sudo maskvol merge /srv/masks/media-dev --delete--delete parses overlayfs whiteouts and opaque directories, then deletes the
corresponding SOURCE paths one by one. It does not use rsync --delete against
the merged TARGET view.
Real merges prompt for confirmation. In non-interactive automation, add
--yes after reviewing a previous dry run.
maskvol export copies only the local UPPER changes:
sudo maskvol export /srv/masks/media-dev /tmp/media-dev-exportThe export contains:
- changed files and directories from UPPER
maskvol-export.manifestmaskvol-delete.manifest, when delete markers exist
Untouched lower SOURCE data is not exported.
Set variables in the environment or in /etc/maskvol.conf:
| Variable | Default | Description |
|---|---|---|
SOURCE_ROOTS |
empty | Optional space-separated roots searched before mount tables for bare SOURCE lookup. |
WORK_ROOT |
/srv/maskvol |
Local storage for lower bind, upper, and work directories. |
REGISTRY_DIR |
/var/lib/maskvol |
Registry metadata, one file per TARGET. |
OVERLAY_OPTS |
redirect_dir=on,index=off,xino=off,metacopy=off |
Overlay mount options tuned for NFS lowerdirs. |
CONFIG_FILE |
/etc/maskvol.conf |
Optional config file path. |
- SOURCE is exposed through a read-only bind mount before overlayfs is mounted.
- TARGET must be absolute and must not equal SOURCE.
testverifies copy-up behavior and checks for SOURCE contamination.purgerefuses to delete upper/work paths outsideWORK_ROOT.mergeandexportuse registered TARGET metadata; operators do not provide SOURCE manually.merge --deletelists delete paths before applying them.rsync --checksumis used so same-size, same-second modifications are not missed.
Run the integration suite as root:
sudo tests/maskvol.test.shThe tests create synthetic data under /tmp, override every runtime root, and
clean up their mounts and files on exit.
maskvol is released under the MIT License.
Created and maintained by chen21019 chen21019@gmail.com.
This project was originally created by chen21019. Copyright (c) 2026 chen21019. Licensed under the MIT License.
Teams often need production-shaped volume data during development, but development containers should not have direct write access to the original data. This project was created to make the safer workflow straightforward: mount a writable mask, inspect exactly what changed, export only the delta when needed, and merge back only after an operator reviews the plan.
It is especially useful as AI agents become part of day-to-day engineering
workflows. Combined with Docker or other container runtimes, maskvol lets an
agent work against an environment that closely resembles production while
remaining isolated from production data. The resulting changes can be exported
quickly as changed files, delete manifests, or reviewable merge plans.