Try describing what you need:

selfhostedworld.com·Guide·Checked October 2026

Where your app data lives

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:

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:

  1. The volumes: lines in the app's example compose file.
  2. 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:

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:

  1. Make Docker wait for the mount. In a systemd drop-in for Docker (sudo systemctl edit docker.service), add RequiresMountsFor. It adds Requires= and After= dependencies for the mounts needed to reach the path.

    [Unit]
    RequiresMountsFor=/mnt/nas/music
    

    The 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.

  2. Mark the share as a network mount in /etc/fstab with _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:

  1. Read the volumes: lines and the docs. List every path the app writes.
  2. Make one folder per app under ~/apps/. Create it as your own user.
  3. Set the container user (user: or PUID and PGID) to match the folder owner.
  4. On Fedora or RHEL, add :Z to the bind mount.
  5. Mount read-only (:ro) anything the app only reads.
  6. Run docker compose down, then up -d, and check that your data is still there.
  7. Write down which data is not on the server, as with OpenPlate's diary.
  8. Never use down -v unless you mean to delete.

Once your data has a known home, set up backups you can restore.

selfhostedworld.com, tracking self-hosted open-source software