When sharing files between a host and a Docker container, there are two main options: "named volumes" and "bind mounts".
They look similar in docker-compose.yml, but their behavior is very different — and that difference can cause serious headaches if you don’t understand it.
Let’s dig into what actually happens, using a real case I ran into.
(Note: I’ll skip the basics of Docker and Docker Compose setup here.)
< environment >
- Infrastructure: AWS EC2
- Architecture: 64-bit ARM
- OS: Ubuntu Server 24.04
- Docker: 28.1.1
- Docker Compose: v2.35.1
The Two Ways to Share Files Between Host and Container
Docker provides two ways to share files between the host and a container:
- Named Volumes
- Bind Mounts
They look nearly identical in configuration, but behave differently when you start up your container.
Let’s look at both.
Named Volume
No "./" prefix:
volumes:
- mediawiki_data:/var/www/html
The shared directory is managed internally by Docker.
In this example, it’s stored somewhere like:
/var/lib/docker/volumes/mediawiki_mediawiki_data
That path is far from your home directory — and only accessible by root.
Bind Mount
Has a "./" prefix:
volumes:
- ./mediawiki_data:/var/www/html
This time, the directory is managed by the host, and the path is relative to where your Dockerfile or compose.yaml exists.
For example, if you run this under the "ubuntu" user’s home directory, it’ll look like:
/home/ubuntu/MediaWiki/mediawiki_data
At first glance, you might think "same thing, different path."
But in reality, there’s a critical difference in how Docker handles these at startup.
The Real-World Case
At work, we decided to manage our internal manuals using "MediaWiki" (the same engine used by Wikipedia).
We wanted to easily copy and back up uploaded files (images, PDFs, etc.) from the host side, so I configured the media directory to be shared between the host and the container.
But as the setup grew, I found myself constantly adding more directories to share in compose.yaml.
Eventually, I got tired of tweaking the file every time I needed another folder, and thought:
Why not just share the entire "/var/www/html" directory between host and container? Then I’ll never have to touch compose.yaml again.
Sounded reasonable. So I did that.
What Is MediaWiki?
It’s the open-source wiki software that powers Wikipedia.
You can check it here:
👉 MediaWiki
The details of MediaWiki itself aren’t important for this story — it’s just the app I was containerizing.
The First Compose File (Named Volume)
Here’s how my initial setup looked:
volumes:
- mediawiki_data:/var/www/html
But I forgot the "./" prefix.
That small mistake made Docker create the shared volume under "/var/lib/docker/volumes/mediawiki_mediawiki_data" — not in my project folder.
That meant:
- All uploaded files were buried deep inside Docker’s internal path
- Only root could access them
So I fixed the "volumes:" section.
The Updated Compose File (Bind Mount)
volumes:
- ./mediawiki_data:/var/www/html
Just added "./". Simple, right?
Since there was no existing "mediawiki_data" directory, I copied the files from the container manually:
sudo docker run --rm -v mediawiki_mediawiki_data:/data:ro -v "$PWD":/backup alpine sh -c 'cd /data && tar czf /backup/mediawiki_data_backup.tgz .'
mkdir -p ./mediawiki_data
sudo tar -xzf mediawiki_data_backup.tgz -C ./mediawiki_data
Everything worked fine.
I updated the repo with the corrected compose.yaml, and all looked good.
The Problem on Another Server
Later, I needed to replicate this setup on a new server.
Same compose.yaml, same image — but MediaWiki wouldn’t start.
Turns out, "/var/www/html" inside the container was completely empty.
Every file required for the app to run was gone.
What happened?
Root Cause
Here’s the relevant section again:
volumes:
- ./mediawiki_data:/var/www/html
When using a "bind mount", Docker always treats the host’s files as the source of truth.
If the host folder exists (even if it’s empty), Docker will overwrite the container directory with it.
So on a clean machine, Docker:
- Creates an empty "./mediawiki_data" folder
- Then syncs it into the container
- Effectively deleting everything inside "/var/www/html"
Brutal. But that’s the expected behavior.
What About Named Volumes?
volumes:
- mediawiki_data:/var/www/html
With a "named volume", Docker manages the directory under "/var/lib/docker/volumes/...".
If it doesn’t exist yet, Docker automatically creates it and copies the container’s existing files into it.
So:
- On the first run -> container files are treated as the source of truth
- On subsequent runs -> host (volume) files take priority
Bind mounts, on the other hand, always trust the host side.
The Difference in Behavior
| Type | First Run | Subsequent Runs | Risk |
|---|---|---|---|
| Bind Mount | Host overwrites container (empty folder = deletes container files) | Host remains the source | High (can delete app data) |
| Named Volume | Container copies files into the volume | Host (volume) becomes source | Moderate |
So if you switch from named volume -> bind mount mid-project, you may end up deleting everything unintentionally.
Workaround
Sure, you could do this the long way:
- Start the container with a named volume
- Backup the files
- Stop the container
- Switch to bind mount
- Restore the files
…but it’s messy, and managing those compose file changes across environments is painful.
Here’s a cleaner approach.
Step-by-Step Fix
Instead of starting from an empty directory, I pre-populate the host folder with the same files the container expects.
# Create target directory
mkdir ./mediawiki_data
# Copy initial files from the container
sudo docker run --rm -u $(id -u):$(id -g) \
-v "$PWD/mediawiki_data":/out \
mediawiki:1.43.1 \
sh -c 'cp -a /var/www/html/. /out/'
What this does:
- Creates a "mediawiki_data" folder on the host
- Starts a short-lived container
- Copies "/var/www/html" from the container into the host directory
- Exits immediately
Now the host folder matches the container’s expected state — no accidental deletion, no surprises.
Summary
- Docker supports two main ways to share files between host and container: "named volumes" and "bind mounts".
- They look similar in YAML but behave very differently.
- "Bind mounts" always trust the host side; an empty host folder will wipe the container’s directory.
- "Named volumes" trust the container at first run, then switch to the host on subsequent runs.
- The difference can cause subtle and destructive bugs if you’re not aware of it.
If your shared directory is supposed to start empty and only receive files from the container, this won’t matter.
But if the container ships with essential files — be careful.
Which One Should You Use?
Opinions differ.
The official Docker docs generally recommend "named volumes", but that’s not always the best idea.
In real projects, you often nuke your Docker environment when testing or debugging —
and "docker rm -v" or similar commands can easily wipe out those volumes (and all your data) in one go.
For production data or important files, I prefer bind mounts:
- Paths are visible and understandable
- You can clearly see what’s being deleted
- You don’t depend on Docker’s internal volume management
Sure, Docker devs want to show that “Docker is safe and convenient,”
but as a sysadmin, I’d rather store my data where I can see it.
Top comments (0)