Skip to main content
RunBook Academy

← All checklists in Docker & Containers

MonthlyStorage

Checklist: Volume and bind-mount data safety review

20 items ·11 critical ·8 warn ·1 info

When to run this

Monthly, and additionally after any change that adds a volume or a bind mount to a production stack. Thirty minutes on a host with a dozen containers. The output is a list of paths that hold data, which is the artefact most teams discover they do not have on the day they need to restore something.

How to run it

Run it on the host, as a user in the docker group or via sudo. Three items need root beyond that: uid-gid-correct reads inside /var/lib/docker/volumes, selinux-labels reads the enforcement mode, and capacity-headroom sizes the volumes tree.

Two items take a specific target rather than sweeping the host. Set them before you run:

VOLUME=platform_pgdata
CONTAINER=platform-db-1

The volumes-in-backup-set item compares against /etc/backup/volumes.include. If your backup tool keeps its include list somewhere else, point the second half of the comm at that file instead — the check is only as good as the file it compares against, and comparing against a stale list is worse than not running it.

Run no-data-in-writable-layer last. docker inspect --size walks every container’s writable layer and takes tens of seconds per container on a busy host.

Reading the results

Most commands here print only the problems: no-anonymous-volumes, volumes-in-backup-set, bind-sources-exist, no-sensitive-host-paths, no-shared-writable-volume, no-data-in-writable-layer, production-volumes-external and no-prune-in-automation are all silent on a healthy host. A page of empty output is the result you want.

The remainder print evidence for a human to judge — named-volumes-inventoried, read-only-where-possible, volume-drivers-reviewed, capacity-headroom. Read those; do not tick them because the command exited zero.

Four items have no command: restore-tested, no-bind-over-populated-path, nfs-mount-options and destruction-path-documented. Nothing on the host can prove any of them, and restore-tested is the single most important line on this page.

Evidence and sign-off

Record the volume inventory and its owners, the date of the last successful restore test, and any exception you granted with its expiry.

  • Volumes reviewed: ______ Unowned: ______ Not in backup: ______
  • Last successful restore test: ___________
  • Operator: _________________ Date: ___________
  • Reviewer: ________________ Date: ___________

Critical11 items

  1. docker ps -q | xargs -r docker inspect --format '{{.Name}}{{range .Mounts}} {{.Name}}{{end}}' | grep -E '[0-9a-f]{64}'
  2. docker volume ls --format '{{.Name}}\t{{.Driver}}'
  3. comm -23 <(docker volume ls -q | sort) <(sort /etc/backup/volumes.include)
  4. docker ps -q | xargs -r docker inspect --format '{{range .Mounts}}{{if eq .Type "bind"}}{{.Source}}{{"\n"}}{{end}}{{end}}' | sort -u | while read -r p; do if [ -n "$p" ] && [ ! -e "$p" ]; then echo "MISSING $p"; fi; done
  5. docker ps -q | xargs -r docker inspect --format '{{.Name}} {{range .Mounts}}{{if eq .Type "bind"}}{{.Source}} {{end}}{{end}}' | grep -E '(docker\.sock|^\S+ /$| / | /etc | /root | /home| /var/lib/docker)'
  6. docker ps -q | xargs -r docker inspect --format '{{$n := .Name}}{{range .Mounts}}{{if .Name}}{{.Name}} {{$n}} {{.RW}}{{"\n"}}{{end}}{{end}}' | awk 'NF==3 && $3=="true"{c[$1]++} END{for (v in c) if (c[v]>1) print v}'
  7. docker ps -q | xargs -r docker inspect --size --format '{{.Name}} {{.SizeRw}}' | awk '$2 > 524288000 {print $1, $2}'
  8. docker compose config --format json | jq -r '(.volumes // {}) | to_entries[] | select(.value.external != true) | .key'
  9. grep -rlnE 'docker (system|volume) prune' /etc/cron* /etc/systemd/system /usr/local/bin 2>/dev/null

Warning8 items

  1. docker volume ls -qf dangling=true
  2. docker ps -q | xargs -r docker inspect --format '{{.Name}}{{range .Mounts}}{{if and (eq .Type "bind") .RW}} {{.Source}}{{end}}{{end}}' | grep -v '^\S*$'
  3. docker volume inspect "$VOLUME" --format '{{.Mountpoint}}' | xargs -r sudo stat -c '%u:%g %a %n'; docker inspect "$CONTAINER" --format 'user={{.Config.User}}'
  4. docker volume ls -q | xargs -r docker volume inspect --format '{{.Name}} {{.Driver}} {{json .Options}}' | grep -v ' local null$'
  5. getenforce 2>/dev/null; docker ps -q | xargs -r docker inspect --format '{{.Name}} {{json .HostConfig.Binds}}'
  6. df -h /var/lib/docker; df -i /var/lib/docker; sudo du -sh /var/lib/docker/volumes

Info1 item