Docker Permission Denied: Which Failure Is Yours?
Docker says permission denied for three unrelated reasons. Tell them apart, then fix the one you have instead of pasting a PUID line that changes nothing.

⚡ The short version
Three unrelated failures print "permission denied" around Docker: your shell cannot open the daemon's socket, a container cannot read a host folder you mounted, or files a container already created as root are now untouchable. The first is a group membership. The second is numeric ownership. The third is neither, which is why setting PUID fixes nothing on its own.
The answers you find are mostly correct and mostly for somebody else’s problem. One thread says run usermod, another says set PUID=1000, a third says chmod 777 the folder. They solve three different things, so two of them will do nothing except leave a worse configuration behind.
Everything below about Docker permission denied errors comes from the projects’ own documentation and source, read on 12 October 2026: Docker’s documentation repository, the Moby client code that prints the error, the Compose specification, and LinuxServer.io’s page on their two variables.
Which Permission Denied Are You Looking At?
Start with where the message came from. That alone separates all three.
| What you see | Where it failed | What actually fixes it |
|---|---|---|
docker itself refuses, before any container starts |
Your shell cannot open the daemon socket | Group membership on the host |
| The container starts, then logs a permission error on a mounted path | The container’s user cannot read or write your folder | Matching numeric ownership |
| You cannot delete or edit files the app created | A container wrote them as root | One ownership change on the host |
The first is easy to confirm, because the client prints two distinct messages that people quote interchangeably. In the Moby client’s request path, a permission error from the operating system produces permission denied while trying to connect to the docker API at <host>, and a missing socket produces failed to connect to the docker API at <host>; check if the path is correct and if the daemon is running (moby/moby, client/request.go, commit 9fabd6d, 12 October 2026).
Error says “Docker daemon socket” instead? Older Docker. The wording changed in Docker 29 and most distribution packages are behind it. Read from the client each release vendors, 12 October 2026:
| Docker CLI | What it prints |
|---|---|
| 27.5.1, 28.0.0, 28.5.1 | permission denied while trying to connect to the Docker daemon socket at <host> |
| 29.0.0, 29.8.2 | permission denied while trying to connect to the docker API at <host> |
Same failure, same fix; the old string is the one to search for.
Permission denied means the socket is there and you cannot open it. The other means nothing is listening, which is a stopped daemon and not a permissions problem at all. They come from different branches of the same function, so the one you get tells you which failure it is.
Why Can’t Your Shell Reach the Daemon?
Because the socket belongs to root and you are not in the group that may open it.
Docker’s post-installation documentation is direct about the design: the daemon binds to a Unix socket rather than a TCP port, root owns that socket, and the daemon itself always runs as root. On start it creates the socket so members of the docker group can reach it.
sudo groupadd docker # only if your distribution did not
sudo usermod -aG docker "$USER"
Then the step people skip: log out and back in, so your group membership is re-evaluated. newgrp docker starts a shell with the group active if you would rather not.
If you ran sudo docker first, expect a second error. Docker's post-installation page documents it: a warning that it cannot load ~/.docker/config.json, because that directory is now root-owned. Delete ~/.docker/ and let it be recreated, losing any custom settings, or chown it back to yourself. It is the third failure below, arriving a week early.
There is a cost, and Docker states it as a warning rather than a footnote: the docker group grants root-level privileges to the user. The reason is structural — anyone who can ask the daemon for a container can ask for one that mounts the host’s root filesystem, and the daemon runs as root. On a single-user home server, where you already hold sudo, that trade is usually fine. On a machine with accounts you do not control it is a privilege grant dressed as a convenience, and it belongs with the rest of your server hardening.
Why Can the Container Not Read Your Folder?
Because a bind mount hands the container your host directory’s ownership, and numbers are all it sees.
This is why a working compose file from someone else’s blog fails on your machine. Their folder is owned by uid 1000 and yours by uid 1001, and nothing in the file records that. Inside the container your username does not exist: the kernel compares numeric UIDs and GIDs, so media on the host and abc in the container are the same user only if they share a number.
The two mount types differ here, and it is documented rather than folklore. Docker’s storage documentation says bind-mounting onto a container directory that already holds files obscures those files, and adds that this behaviour differs from that of volumes — an empty named volume mounted over a populated container directory gets that directory’s contents copied into it instead. That is why named volumes feel like they just work: the image’s prepared directory, with whatever ownership its author set, is what ends up in the volume. A bind mount discards that preparation and substitutes your folder and your numbers.
⚠️ A missing host path is created for you, and not as you. Docker's bind-mount documentation states that the -v form creates the source directory on the host when it does not exist, while the --mount form errors unless you pass bind-create-src. The daemon doing the creating runs as root, so a typo in a host path does not fail loudly — it produces an empty, root-owned directory and the app comes up with none of your data. It is also how a backup ends up archiving an empty folder, which the Docker volume backup guide meets from the other side.
PUID and PGID, or Compose’s user Setting?
Pick by who published the image. The two are not interchangeable, and on a LinuxServer image they are not additive either.
PUID and PGID are not Docker features. They are a convention in LinuxServer.io’s images: an init step inside the container reads the two variables and adjusts its internal user to match, so files it writes come out owned by the numbers you gave. An official image from a project’s own maintainers reads nothing of the kind, and the variables sit in the compose file as decoration — this site’s Navidrome guide hits that wall, because the project names them as something people try that does not work.
The general mechanism is Compose’s own. The specification defines user as overriding the user the container process runs as, defaulting to the image’s USER instruction and falling back to root when the image sets none. It takes numbers.
services:
app:
image: ghcr.io/example/app:latest
user: "1000:1000" # official images: Compose's own setting
volumes:
- ./data:/data
services:
app:
image: lscr.io/linuxserver/example:latest
environment:
- PUID=1000 # LinuxServer images: their convention
- PGID=1000
volumes:
- ./data:/config
Keeping them apart is not tidiness, and it is the thing on this topic I did not expect to find. LinuxServer’s page on the variables opens with a notice that recent Docker versions introduced the --user flag, their images are not yet compatible with it, and they recommend continuing to use PUID and PGID (read in linuxserver/docker-documentation at docs/general/understanding-puid-and-pgid.md, commit d8900b1, 12 October 2026). Adding user: to a LinuxServer container is not extra safety on top of the variables; it is the combination the maintainers say does not work.
One more setting is worth knowing before you reach for chmod 777. Compose’s group_add gives the container’s user additional supplementary groups, by name or number; the specification’s own example is several containers running as different users that all read and write one file on a shared volume, owned by a group they all carry.
Why Didn’t Setting PUID Fix Anything?
Because it governs what happens next, and your problem is already on disk.
This is the gap in every page-one result for this error. The variables, and user:, set the identity of the process from the next start onward; they do not walk your data directory and re-stamp it. If the container ran as root for a week before you noticed, a week of root-owned files is sitting there, the newly unprivileged process cannot write into them, and the app fails exactly as before — so the obvious conclusion is that the setting did not take.
Check the numbers rather than reasoning about them. Two commands, and they have to agree:
ls -ln /srv/appdata # numeric owner on the host, not the name
docker compose exec app id # who the container is actually running as
ls -ln matters because ls -l resolves numbers into whatever names your host happens to have, which is the one translation that hides the mismatch. If its third and fourth columns do not match the uid and gid that id prints inside the container, you have found it.
The fix is one ownership change, on the host, aimed at the directory you mounted:
sudo chown -R 1000:1000 /srv/appdata
docker compose up -d
Point chown -R at the mount, never at its parent. Recursive ownership changes have no undo, and the directory you mounted usually sits inside something you care about. Read the exact path back before you press return. Restart the stack afterwards too: a container still running as root will create the next batch the same way.
There is also a case where none of this is yours to solve. If an image cannot run as anything but root and writes to a path you need to read, the honest answer may be a named volume plus a backup that reaches it from a throwaway container, rather than a bind mount you keep re-owning — the same line the Docker for self-hosting guide takes on choosing a storage type.
Does Rootless Docker Make This Go Away?
It does not remove the problem, it inverts it — and if you are running rootless without realising, that inversion is probably what is confusing you.
Rootless mode runs the daemon and the containers inside a user namespace, so neither holds real root on the host — Docker contrasts it with userns-remap, where the daemon itself still runs privileged. Its mapping documentation states both halves of the ownership consequence: container UID 0 maps to the host UID of the user running rootless Docker, the result of id -u, while container UID n for n of 1 or more maps to subuid + (n - 1). Then the sentence that resolves the confusion — in rootless mode, files owned by your host user appear as owned by root inside the container.
So the default case flips. A container running as root writes files that belong to you, and the ownership pain disappears with no variable set at all. Meanwhile a PUID=1000 copied from a rootful install now points at a subordinate UID with nothing to do with your account, and user: "1000:1000" lands inside your subuid range rather than on you.
It is not free. Rootless mode needs newuidmap and newgidmap on the host, from the uidmap package on most distributions, and at least 65,536 subordinate UIDs and GIDs allocated to your user in /etc/subuid and /etc/subgid. None of it was tested for this post — the machine it was written on has no daemon — so every claim in this section is Docker's documentation rather than a measurement.
My read: on a new single-purpose box, if ownership is what is annoying you, rootless is a real answer and the security argument comes free. On a working stack with data in it, switching daemon modes to avoid one chown is a large change aimed at a small problem.
Frequently Asked Questions About Docker Permission Denied
Why does Docker say permission denied as soon as I run a command?
Because the Docker daemon listens on a Unix socket that root owns, and your user is not in the group allowed to open it. Docker’s own post-installation guide covers the fix: create the docker group if your distribution did not, add your user to it, then log out and back in so the membership is re-evaluated. The newgrp command starts a shell with the new group active if you would rather not log out.
Is adding my user to the docker group safe?
It is convenient and it is not a small decision. Docker’s post-installation documentation carries an explicit warning that the docker group grants root-level privileges to the user, because anyone who can talk to the daemon can start a container that mounts the whole host filesystem. On a single-user home server that is usually an acceptable trade. On a box with other accounts on it, it is not.
Why did setting PUID and PGID change nothing?
Because those variables decide who the container runs as from now on, and they cannot reach back and change files that already exist. A container that ran as root before you set them left a folder of root-owned files behind, and the new unprivileged user cannot write into it. Change the ownership on the host yourself, once, then let the variables keep it that way.
Should I use PUID and PGID or the Compose user setting?
It depends on who built the image, and the two are not interchangeable. PUID and PGID are a LinuxServer.io convention that only their images read. The Compose user setting is part of the Compose specification and overrides the user the container process runs as on any image. LinuxServer’s own documentation says their images are not yet compatible with the Docker user flag, so setting both on one of their containers is the wrong combination rather than belt and braces.
How do I fix files a container already created as root?
Change their owner on the host with chown, pointed at the single directory you mounted and nothing above it. Find the right numeric values first by running id as the user that needs access, and confirm what the container is actually running as by running id inside it. Then restart the stack so the container picks up the user you configured, or it will create the next batch as root too.
Does rootless Docker make the bind mount problem go away?
It inverts it. In rootless mode the daemon and the containers run inside a user namespace, and container UID zero maps to your own host UID, so a container running as root writes files that belong to you. Docker’s mapping documentation states the other half plainly: files owned by your host user appear as owned by root inside the container. A PUID of 1000 copied from a normal install is then pointing at the wrong identity.
Product links on this site are plain links. We earn nothing from them — see our disclosure policy.