Docker volume permissions

Pick an app, or enter your own image and user, and get the commands that show who owns the data directory and the exact fix for it. Commands that change the server are marked; nothing here runs anything.

Each volume gets its own subdirectory here, named after its Compose volume. Use the directory your compose.yaml sits in (.) or an absolute path such as /srv/n8n.

n8n (main) runs as 1000:1000 (user node).

From the catalog's user data for n8n, with its sources on the app page. Sources and notes on the n8n page

What goes wrong

A bind mount keeps the owner the directory has on the host, and the container sees only numbers. When ./n8n-data is not owned by 1000:1000, the process cannot write to it, logs Permission denied and usually exits.

In the containerHost directory
/home/node/.n8n./n8n-data
Before you run anything
  • Do not use chmod 777 (or chmod -R 777). It lets every user and process on the server read and change the data, it hides the real owner problem, and some apps refuse to start with it (for example PostgreSQL and SSH keys).
  • user: works only if the image supports an arbitrary user. Check the image's documentation before you change it.

1. Check who owns it (read-only)

These commands only read. Compare the owner of each path with 1000:1000.

  • ls -lnd ./n8n-dataRead-only Numeric owner and mode of the directory on the host.
  • docker exec <project>-n8n-1 idRead-only The user the running container actually uses.
  • docker run --rm --entrypoint id docker.n8n.io/n8nio/n8n:2.42.6Read-only The image's default user, in a throwaway container, when the container keeps exiting.
  • idRead-only Your own uid and gid on the host, for user: in Compose.
check.sh
ls -lnd ./n8n-data
docker exec <project>-n8n-1 id
docker run --rm --entrypoint id docker.n8n.io/n8nio/n8n:2.42.6
id

2. Fix it

Recommended Give the directory to 1000:1000

Stop the container, run these for each path, then start it again with docker compose up -d. The path is explicit: never run a recursive chown on a parent directory.

Changes the server. These commands change ownership on the server. Read them, check the path, and stop the container first.

  • sudo mkdir -p ./n8n-dataChanges the server Creates the directory if it is missing.
  • sudo chown -R 1000:1000 ./n8n-dataChanges the server Gives the directory and everything in it to the container's user.
fix.sh
sudo mkdir -p ./n8n-data
sudo chown -R 1000:1000 ./n8n-data

Alternative Use a named volume instead

When you do not need to edit the files from the host, let Docker manage the directory: a new named volume takes its owner from the image. Move existing data into it first; changing the file alone starts with an empty volume.

compose.yaml
services:
  n8n:
    volumes:
      - n8n-data:/home/node/.n8n
volumes:
  n8n-data:

Alternative Run the service as your own user

Set user: to the uid:gid that id prints and give the directory to that user. Only do this when the image documents that it runs as any user: some images need their own user and home directory.

Changes the server. These commands change ownership on the server. Read them, check the path, and stop the container first.

  • sudo chown -R "$(id -u):$(id -g)" ./n8n-dataChanges the server Gives the directory to your own user, which the service then runs as.
fix.sh
sudo chown -R "$(id -u):$(id -g)" ./n8n-data
compose.yaml
services:
  n8n:
    user: "1000:1000"

What it does

It finds out why a container gets Permission denied on its data directory and gives the exact fix. A container sees the numeric owner of a mounted directory, not a name, so a directory that belongs to another uid, often root after a manual mkdir or a restored backup, cannot be written to.

How it works

  1. For a catalog app, the user each container runs as and the paths it writes come from the catalog's verified data (ops.runAs, with sources on the app page). For your own image, you enter the uid and the path.
  2. The check commands only read: ls -lnd on the host, id in the container, and for a named volume ls in a throwaway container that mounts it read-only.
  3. The fix depends on the mount and on rootless Docker: a chown of the exact directory, the same chown from inside Docker's namespace for rootless, user: in Compose when the image allows it, or a named volume. Commands that change the server are marked.

Example

n8n runs as node, 1000:1000, and writes to /home/node/.n8n. With a bind mount in the directory of compose.yaml on rootful Docker, the check is:

check.sh
ls -lnd ./n8n-data
docker exec <project>-n8n-1 id
docker run --rm --entrypoint id docker.n8n.io/n8nio/n8n:2.42.6
id

and when the owner is not 1000:1000, the fix (with the container stopped) is:

fix.sh
sudo mkdir -p ./n8n-data
sudo chown -R 1000:1000 ./n8n-data

Do not use chmod 777 (or chmod -R 777). It lets every user and process on the server read and change the data, it hides the real owner problem, and some apps refuse to start with it (for example PostgreSQL and SSH keys).

Limits

  • SELinux and AppArmor are not covered: on Fedora, RHEL and similar systems a bind mount may also need a :z or :Z label, which this tool does not write.
  • Docker Desktop on Windows and macOS is not covered; its file sharing has its own permission model.
  • NFS and SMB shares can map or squash ids on the server; a chown on the client may not apply.
  • The catalog gives the image's default user. A user: or a PUID/PGID setting in your own file changes it.

A container that keeps restarting? Walk through the restart loop wizard

Methodology: how the numbers are worked out

Sources

  1. Docker Engine: bind mounts
  2. Docker Engine: volumes
  3. Docker Engine: rootless mode
  4. Compose file reference: services, user
  5. chown(1)
  6. subuid(5)

Updated

Next step

Docker Compose Generator

A free public tool by ANAROQ, a company building compute infrastructure. What ANAROQ is building