A container is disposable, but your data is not. This guide shows where an app keeps its files, how to put them in one folder you can see, and what breaks when permissions or mounts go wrong.
The container forgets
Docker runs each app in a container. By default, the data in a container sits in a temporary writable layer, and removing the container also removes its data. Updating an app means recreating its container, so anything the app wrote inside it is gone after the update.
Data survives only if it lives outside the container, in a volume or a bind mount. The app sees a normal folder. Docker connects that folder to a place on the host. Docker Compose basics shows where these lines go in a compose file.
Named volumes and bind mounts
Docker offers two ways to keep data.
A named volume has a name, such as navidrome-data. Docker manages it. On Linux, the files sit in a directory like /var/lib/docker/volumes/<name>/_data. You rarely look inside, and you need root to do so.
A bind mount maps a folder you choose on the host into the container. A file or directory on the host machine is mounted into the container. You see the files in your file manager or with ls.
Docker's own docs call volumes the preferred mechanism, and they are a fine choice for a team. For a novice, we suggest bind mounts, for three reasons:
- You can see every file the app owns.
- One folder per app is easy to copy, back up and delete.
- Nothing hides in a Docker directory you have to hunt for. Docker itself warns that volumes are not a good choice if you need to access the files from the host.
The cost: a bind mount can change the host. A process in the container can create, modify or delete files on the host, and bind mounts are writable by default. Mount only the folder an app needs, and add :ro (read-only) where it only reads.
One folder per app
Pick one parent folder and give each app a subfolder:
~/apps/
navidrome/
compose.yaml
data/
Create the folders yourself, as your own user, before you start the app:
mkdir -p ~/apps/navidrome/data
Compose will create a missing host folder for you. The Docker service runs as root, so root usually owns that folder, and that causes the permission errors described below. Make the folder first.
Down with -v deletes named volumes
docker compose down stops and removes your containers. It keeps named volumes. Add -v and it removes the named volumes declared in the compose file, plus anonymous volumes. The app's database goes with them.
Never add -v out of habit. Bind-mounted folders are not on that list, which is one more point for bind mounts. Still, copy your data before you experiment.
Find what an app stores
Each app documents its paths. Read two things:
- The
volumes:lines in the app's example compose file. - The "data" or "configuration" page in its docs.
Navidrome, a music streaming server, is a clear example. Its Docker docs use this compose file:
services:
navidrome:
image: navidrome/navidrome:latest
user: 1000:1000
ports:
- "4533:4533"
restart: unless-stopped
volumes:
- "/path/to/data:/data"
- "/path/to/your/music/folder:/music:ro"
Left of each colon is the host folder, right is the path inside the container. Navidrome stores its database and cache in /data. It reads your music from /music. The :ro ending makes that mount read-only, so a bug cannot change your music files.
That tells you what to back up: the data folder. Your music is a separate library with its own copy.
Two lines in that example differ from the defaults in Docker Compose basics. Pin a version tag in place of latest, and write the port as "127.0.0.1:4533:4533" until you have chosen a front door.
Permissions: the user inside versus the folder outside
Files on Linux belong to a numeric user ID. The app inside the container runs as some user ID, and the host folder has an owner ID. If they do not match, the app cannot write.
Navidrome's docs describe both symptoms. If /data is not writable, the container stops at startup. If /music is not readable, Navidrome starts but the library is empty, and the log says open /music: permission denied. An empty library with a running app is almost always this.
Find your own IDs:
id -u
id -g
Then make the app run as that user and own its folder. Images do this in one of two ways:
user:in the compose file. This overrides the user the container process runs as. Navidrome uses this, and its docs sayPUIDandPGIDdo nothing on that image.PUIDandPGIDvariables. The LinuxServer.io images use these. They map the container's internal user to a user on the host. Many other images ignore them.
Read the app's docs to see which one applies. Do not set both and hope.
If the folder has the wrong owner, fix it:
sudo chown -R 1000:1000 ~/apps/navidrome/data
Use your own IDs in place of 1000:1000. Never run chmod 777 to make the error go away. It lets every user and process on the host write to the folder. Fix the owner instead. For read-only data such as music, a read bit for others (chmod -R o+rX) is enough.
Do not remove user: to run as root either. Navidrome's docs tell you not to. A root app that writes files leaves root-owned files you must sudo to touch.
SELinux hosts
Fedora, RHEL and related systems run SELinux, which can block a container from reading a bind mount even with the right owner. The symptom is the same: permission denied. Add a label option to the mount:
volumes:
- "./data:/data:Z"
Z marks the content as private to this container, and z marks it as shared among several containers. Use Z for an app's own data folder. Use z for a folder two apps share. Docker says to use extreme caution with these options: they relabel the host folder. Never put them on your home directory or a system folder. On a host without SELinux, Compose ignores the option, so you can leave it out on Debian and Ubuntu.
Network shares
Many people keep media on a NAS and mount it over NFS or SMB. After a reboot, Docker can start before the share is mounted. The container then finds an empty folder where your library should be. A scan of that empty folder can make the app think your music is gone.
Two defenses:
-
Make Docker wait for the mount. In a systemd drop-in for Docker (
sudo systemctl edit docker.service), addRequiresMountsFor. It addsRequires=andAfter=dependencies for the mounts needed to reach the path.[Unit] RequiresMountsFor=/mnt/nas/musicThe trade-off: if the share fails to mount, Docker does not start at all, so every app on the server stays down until you fix the mount.
-
Mark the share as a network mount in
/etc/fstabwith_netdev. systemd then treats it as needing the network and orders it after the network is up.
A third guard sits in Compose. With the long mount syntax, set create_host_path to false. Docker then refuses to start the container if the source folder is missing. This does not help if the source is the mount point itself, because /mnt/nas/music usually exists as an empty folder even when the share is not mounted. Point the source at a folder inside the share:
volumes:
- type: bind
source: /mnt/nas/music/library
target: /music
read_only: true
bind:
create_host_path: false
When the share is missing, /mnt/nas/music/library does not exist, and the container fails with bind source path does not exist in place of an empty library.
Databases need more care
Do not copy a running database's folder and call it a backup. For PostgreSQL, the server must be shut down to get a usable file-level backup. Stopping connections is not enough. The same caution applies to any app that keeps a database file in its data folder: a copy taken mid-write can be broken.
Stop the app first, or use the database's own dump tool. Backups you can restore covers both, and how to test that a backup works.
Not everything lives on the server
Some apps keep your data on the device you use. OpenPlate is one. Its compose file says the server stores nothing, and the diary lives in your browser's IndexedDB on the device you use it from. A backup of the server folder does not cover that diary. Use the in-app JSON export, and keep the file somewhere safe. Clearing browser data deletes the diary.
Map your data
Before you rely on any app, do this:
- Read the
volumes:lines and the docs. List every path the app writes. - Make one folder per app under
~/apps/. Create it as your own user. - Set the container user (
user:orPUIDandPGID) to match the folder owner. - On Fedora or RHEL, add
:Zto the bind mount. - Mount read-only (
:ro) anything the app only reads. - Run
docker compose down, thenup -d, and check that your data is still there. - Write down which data is not on the server, as with OpenPlate's diary.
- Never use
down -vunless you mean to delete.
Once your data has a known home, set up backups you can restore.