SSD Nodes Learn 🎉 VPS from $5.50/mo
Guides Matt ConnorBy Matt Connor

Where Nextcloud in Docker Stores Files

Find the Nextcloud data directory inside the container, the host path behind the volume, and everything else a restorable backup needs beyond user files.

Where Nextcloud in Docker stores files

Nextcloud in Docker stores files in a data directory inside the container, and the real location on your server is whichever volume or bind mount you attached to it. With the linuxserver.io image, lscr.io/linuxserver/nextcloud, user files live in /data, and the Nextcloud installation with its config.php lives in /config. Both of those are container paths. One command prints the host path behind them, and the rest of this guide covers the harder half of the question: everything the data directory does not hold.

Pin the image tag. Paths belong to an image rather than to Nextcloud, and a floating tag can move under you. As of August 2026 the current stable tag for this image is 34.0.3.

services:
  nextcloud:
    image: lscr.io/linuxserver/nextcloud:34.0.3
    container_name: nextcloud
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
    volumes:
      - nextcloud_config:/config
      - nextcloud_data:/data
    ports:
      - 443:443
    restart: unless-stopped

  nextcloud-db:
    image: mariadb:11.8
    container_name: nextcloud-db
    environment:
      - MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD}
      - MARIADB_DATABASE=nextcloud
      - MARIADB_USER=nextcloud
      - MARIADB_PASSWORD=${NEXTCLOUD_DB_PASSWORD}
    volumes:
      - nextcloud_db:/var/lib/mysql
    restart: unless-stopped

volumes:
  nextcloud_config:
  nextcloud_data:
  nextcloud_db:

The two passwords come from a .env file next to the compose file, so they stay out of the compose file itself. That is three volumes in the answer, and only one of them holds user files.

Those container paths come from the documentation for that one image. A different Nextcloud image lays out its filesystem differently and keeps the installation under its own web root, so a path copied from a forum post is a guess. Read the truth from the container you are running.

docker inspect nextcloud

The Mounts section of that output lists every mount, with Source on the host side and Destination on the container side. That listing answers the question for your setup, whichever image you chose.

How do I find the real host path behind the volume?

A named volume is managed by Docker, so you do not pick its path. You ask for it.

docker volume ls
docker volume inspect nextcloud_nextcloud_data

The name matters. Docker Compose prefixes volume names with the project name, which defaults to the name of the directory holding your compose file, so a volume written as nextcloud_data in the file usually exists as nextcloud_nextcloud_data on disk. docker volume ls shows the real names. The inspect output looks like this, trimmed:

[
    {
        "CreatedAt": "2026-08-18T09:12:44Z",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/nextcloud_nextcloud_data/_data",
        "Name": "nextcloud_nextcloud_data",
        "Scope": "local"
    }
]

Mountpoint is the answer. Read it from the command instead of assuming it, because it moves. Under rootless Docker the whole Docker data root sits inside the home directory of the user running the daemon, so that path starts somewhere else.

A bind mount removes the question. Write - /srv/nextcloud/data:/data in the compose file and the host path is the one you typed, which docker inspect reports as the Source. The choice changes more than the path, because named volumes and bind mounts behave differently around ownership and backups.

Why the data directory is not a backup

The Nextcloud manual lists five things a backup has to keep: the config folder, the custom apps folder, the data folder, the theme folder, and the database. With this image the config, apps and theme folders all live under /config, while the database runs in its own container with its own volume. Copy /data on its own and you have saved the least interesting part of the problem.

The database matters because the web interface never lists a directory. It lists rows from the file cache, which is why the manual tells you to run a scan after copying files into the data directory by hand. Restore /data next to an empty database and you have bytes with no index: no users, no shares, nothing in the file list. Restore the database next to an empty /data and every row points at a file that is gone.

config.php holds the database credentials and the trusted domains. It also holds the instance id, which is the name of the app data folder inside the data directory. Ask the running instance instead of trusting either value from memory.

docker exec -it nextcloud occ config:system:get datadirectory
docker exec -it nextcloud occ config:system:get instanceid

The first prints the data directory this instance actually uses, which is /data here. This image ships an occ wrapper on the path, so run it straight through docker exec. Do not copy the longer sudo and php occ form from the Nextcloud manual, which is written for an installation outside a container.

What is quietly filling the data volume?

Previews and per-user history sit in the same volume as the files, and neither one shows up in the storage figure a user sees in the web interface.

  • Previews are generated thumbnails. They live in the app data folder inside the data directory, named appdata_ followed by the instance id.
  • Deleted files stay in the trash. trashbin_retention_obligation defaults to auto, which keeps them for 30 days and removes them after that only when space is needed. Deleted files still count against the user quota, and when the quota is exceeded the retention setting is ignored and the trash is trimmed until the quota fits again.
  • Old versions stay as well. versions_retention_obligation also defaults to auto. The Versions app never uses more than 50% of the space a user currently has free, and when it prunes it deletes the oldest versions first while keeping the two most recent. A version that a user named by hand is never deleted.

Measure before you delete anything.

docker exec -it nextcloud sh -c 'du -sh /data/*'
docker exec -it nextcloud sh -c 'du -sh /data/appdata_*'

The first line gives one number per user folder plus the app data folder. If the app data number is large, previews are the reason. The cleanup commands below are documented, and every one of them destroys data on purpose.

docker exec -it nextcloud occ trashbin:cleanup --all-users
docker exec -it nextcloud occ versions:cleanup alice
docker exec -it nextcloud occ preview:cleanup

preview:cleanup removes every generated preview, and Nextcloud generates them again as users open those files, so the space comes back gradually and the CPU pays for it. If the volume is only one part of a wider disk problem, old images and stale build cache are usually the other part.

Why do files I copy onto the host not appear in Nextcloud?

Because Nextcloud reads its file cache in the database, not the directory. Your copy created a file on disk with no matching row, so the web interface has nothing to list. The manual names this exact case: a scan is needed after copying files directly into the data directory.

docker exec -it nextcloud occ files:scan --path="/alice/files/Photos"
docker exec -it nextcloud occ files:scan --unscanned -v
docker exec -it nextcloud occ files:scan --all

The --path argument also shows the layout inside the data directory: each user has a folder named after their username, and files inside it holds what they see in the web interface. Scan one path when you know where the files went. --all walks every user and takes a long time on a large instance. --unscanned only touches files that are marked as not yet fully scanned. -v prints each file as it is processed, which is the difference between a command that looks stuck and one you can watch.

Ownership decides whether the scan is enough. A file the container user cannot write is indexed and then refuses to move, so the listing looks correct while the rename or the delete from the web interface fails.

Why do writes fail after I set PUID and PGID?

Because the kernel compares numbers, not names. PUID and PGID set the numeric user id (uid) and group id (gid) that the container process runs as. Every file on the host carries a numeric owner too. When the two numbers differ, the write is refused, whatever the names look like on either side.

docker exec -it nextcloud id abc
sudo ls -ln /var/lib/docker/volumes/nextcloud_nextcloud_data/_data

id abc prints the uid and gid the container is really using, which are the PUID and PGID you set. ls -ln prints numeric owners, and the -n matters: plain ls -l translates those numbers through the host's own user list and shows a name that means nothing inside the container. Compare the two numbers.

Then test the write instead of guessing.

docker exec -u abc -it nextcloud touch /data/writetest

A Permission denied naming /data is the confirmation. Fix the ownership from inside the container, then run the same test again.

docker exec -u 0 -it nextcloud chown -R abc:abc /data
docker exec -u abc -it nextcloud touch /data/writetest
docker exec -u abc -it nextcloud rm /data/writetest

Do it from inside for a reason. Under rootless Docker the container's user ids are mapped through the subordinate range in /etc/subuid, so uid 1000 inside the container belongs to a much higher uid on the host. A host-side chown 1000:1000 then sets an owner the container cannot use, and the write still fails. Running chown inside the container goes through the same mapping the Nextcloud process itself uses, so the numbers line up by construction. This is also why PUID and PGID have to match the owner on disk before you start debugging anything else.

How do I back up so the restore actually works?

Take the database and the folders from the same moment. Maintenance mode stops logins, so no upload lands between the dump and the copy.

docker exec -it nextcloud occ maintenance:mode --on
docker exec nextcloud-db mariadb-dump --single-transaction -u nextcloud -p"$NEXTCLOUD_DB_PASSWORD" nextcloud > nextcloud-sqlbkp.sql
docker run --rm -v nextcloud_nextcloud_data:/data:ro -v "$PWD":/backup alpine:3.22 tar czf /backup/nextcloud-data.tgz -C /data .
docker run --rm -v nextcloud_nextcloud_config:/config:ro -v "$PWD":/backup alpine:3.22 tar czf /backup/nextcloud-config.tgz -C /config .
docker exec -it nextcloud occ maintenance:mode --off

Notice what the dump line does not have: a -t flag. A TTY rewrites line endings, and a SQL dump that has passed through one is corrupt in a way you only discover during the restore. Notice also that a password on a command line is visible in ps output while the command runs, so read it from your .env file into the shell rather than typing it. Older database images ship mysqldump instead of mariadb-dump, and the manual documents both.

To restore, load the dump into an empty database, unpack both archives into fresh volumes, start the containers, then turn maintenance mode off. If the folders and the dump came from different moments, the file cache and the disk disagree, and occ files:scan --all repairs only one direction. It finds files that exist without a row. It cannot bring back a file that a row points at.

Keep the result off the server. A copy that lives inside the same VPS dies with the VPS, which is why restic to an off-server repository belongs in this loop, and why a provider snapshot is a different tool from a backup. If you are still building the stack, a full Nextcloud install on a VPS covers the reverse proxy and the TLS (transport layer security) certificate that this guide leaves out.

FAQ

Where is the Nextcloud data directory in a Docker container?

With the linuxserver.io image it is /data inside the container, and the installation with config.php is under /config. Those are container paths. For the host path, run docker inspect nextcloud and read the Source value in the Mounts section, or run docker volume inspect on the volume and read Mountpoint. Other Nextcloud images use different container paths, so check the documentation for the tag you pinned, and confirm with docker exec -it nextcloud occ config:system:get datadirectory.

Why do files I copy into the volume not show up in Nextcloud?

Nextcloud lists rows from the file cache in its database rather than reading the directory, so a file that arrives without going through Nextcloud has no row and stays invisible. Run docker exec -it nextcloud occ files:scan --path="/alice/files/Photos" for one folder, or occ files:scan --all for every user. If the files appear but then refuse to move or delete, the cause is ownership: the container user must be able to write them.

Is a copy of the data volume enough to restore Nextcloud?

No. The data volume holds file content. The database holds the file index along with users and shares, and config.php holds the database credentials and the instance id. A working restore needs the data folder, the config folder, the database, and the custom apps and theme folders if you use them. Take all of them from the same moment, because a database that is newer than the files points at files that do not exist.

Why is my data volume much larger than the files my users see?

Previews, deleted files and old versions live in the same volume and none of them appear in the figure a user sees. Measure with docker exec -it nextcloud sh -c 'du -sh /data/*'. The trash keeps deleted files for 30 days by default and clears them earlier only when space is needed, and the Versions app can use up to half of the free space a user currently has. Clear them with occ trashbin:cleanup --all-users, occ versions:cleanup alice and occ preview:cleanup, and expect previews to grow back as people open their files.

Can I move the Nextcloud data directory to another disk?

Mount the new location at the same container path instead of changing the path Nextcloud knows. Stop the container, copy the old contents to the new disk with ownership preserved (cp -a or rsync -aAX), point the volume or bind mount at the new location in your compose file, then start it again. Nextcloud still sees /data, so no row in the database has to change. Verify with docker exec -it nextcloud occ config:system:get datadirectory and one test upload.