CertGrid CertGrid
Hands-on Lab·Docker

Docker Volumes and Bind Mounts

Anything written inside a container dies with it. Volumes and bind mounts are the two ways to stop that, and they are not interchangeable - this guide shows what each is for, proves data survives, and demonstrates a read-only mount refusing a write.

Storage and Networking Guide 12 of 46 Beginner

Tested on the versions above. Volume mountpoints, container IDs and file timestamps differ per host. Match the shape of the output, not the exact strings.

One Docker host is all this guide needs. Nothing here depends on a second machine, and the hardware above is modest on purpose - a 2 core, 4 GB VM runs everything in this path.
Server NameIP AddressOSRolesCPURAMHDD
DOCKER01192.168.0.21Ubuntu 26.04 LTSDocker Host2 Core4 GB50 GB

Before you start

  1. Create a named volume

    A volume is storage Docker manages for you, living under the Docker root directory. You never need to know the host path to use it, which is exactly the point - the volume is addressed by name, so the same command works on any host.

    bash
    docker volume create cg-datacg-datadocker volume ls --filter name=cg-DRIVER    VOLUME NAMElocal     cg-data

    Expected resultThe volume name echoed, then one row in the listing.

    Success conditioncg-data appears with driver local. The volume exists independently of any container.

  2. See where it actually lives

    The mountpoint is on the host filesystem under Docker's root. You can look, but treat it as Docker's private storage - manipulating it directly behind Docker's back is how volumes end up in odd states.

    bash Example session
    docker volume inspect cg-data --format "{{.Mountpoint}} driver={{.Driver}}"/var/lib/docker/volumes/cg-data/_data driver=local

    Expected resultA path under /var/lib/docker/volumes and the driver name.

    Success conditionYou get a path. On Docker Desktop this path is inside the VM, not on your laptop's filesystem.

  3. Prove the data outlives the container

    This is the whole point, so demonstrate it rather than trusting it. The first container writes a file and is deleted immediately - --rm removes it on exit. A completely separate container then reads the file back.

    bash Example session
    docker run --rm -v cg-data:/data alpine sh -c "echo hello-from-volume > /data/note.txt; ls -l /data"total 4-rw-r--r--    1 root     root            18 Aug 20 05:23 note.txtdocker run --rm -v cg-data:/data alpine cat /data/note.txthello-from-volume

    Expected resultThe file listed by the first container, then its contents read by a second, unrelated container.

    Success conditionThe second container prints the text. Both containers are gone; the data is not.

  4. Bind mounts map a host directory instead

    A bind mount points at a specific path you control on the host. Use it when you want to see and edit the files yourself - local development, or serving content you maintain. The trade-off is portability: the path must exist on every machine that runs the container.

    bash Example session
    mkdir -p /tmp/cg-site && echo "<h1>Bind mount works</h1>" > /tmp/cg-site/index.htmldocker run -d --name cg-bind -p 8081:80 -v /tmp/cg-site:/usr/share/nginx/html:ro nginx:alpine9b75eaf23d489f4932c941c15c219ae04fecae38117346c64fdd0059c7a43232curl -s http://localhost:8081<h1>Bind mount works</h1>

    Expected resultThe HTML you wrote on the host served back by nginx in the container.

    Success conditioncurl returns your content. Edit the file on the host and reload - the change appears with no rebuild and no restart.

  5. Read-only mounts are enforced by the kernel

    The :ro suffix mounts the source read-only inside the container. This is not advisory - a write is refused by the filesystem itself. Mount content read-only whenever the container has no legitimate reason to modify it.

    bash
    docker exec cg-bind sh -c "touch /usr/share/nginx/html/x"touch: /usr/share/nginx/html/x: Read-only file systemdocker inspect cg-bind --format "{{json .Mounts}}"[{"Type":"bind","Source":"/tmp/cg-site","Destination":"/usr/share/nginx/html","Mode":"ro","RW":false,"Propagation":"rprivate"}]

    Expected resultThe write refused, and "RW":false in the mount record confirming why.

    Success conditionRead-only file system. If the write succeeded, the :ro suffix was omitted - check the Mode field.

  6. Clean up

    Removing a volume destroys its contents permanently and Docker will not ask twice. Name volumes explicitly; never reach for a blanket prune while learning.

    bash
    # docker volume rm DELETES THE DATA. There is no undo and no confirmation prompt.docker rm -f cg-bindcg-binddocker volume rm cg-datacg-data

    Expected resultThe container name, then the volume name.

    Success conditiondocker volume ls no longer lists cg-data. The bind-mounted directory /tmp/cg-site still exists - Docker never owned it.

Troubleshooting

Official sources