You run docker ps on a Linux machine and Docker Engine 27 or 28 answers:
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.47/containers/json": dial unix /var/run/docker.sock: connect: permission deniedThe CLI found the daemon’s socket file, but the kernel refused to let your user connect to it. The daemon is probably running fine; this is a Unix file permission problem. The API version in the URL (v1.47 here) changes with the client version. Older clients start the message with “Got permission denied”, and the Docker 29 CLI shortens it to permission denied while trying to connect to the docker API at unix:///var/run/docker.sock.
Quick fix checklist
- Run
ls -l /var/run/docker.sock: it should besrw-rw----owned byroot docker. - Run
id -nGand check whetherdockeris in the list for your current shell. - If not:
sudo usermod -aG docker $USER, then log out and back in (or runnewgrp dockerin this terminal). - Restart long-running services (CI agents, IDE servers, tmux) that were started before the group change.
- If the message instead says “Is the docker daemon running?”, start the daemon (
sudo systemctl start docker) or Docker Desktop. - Run
docker context lsandecho $DOCKER_HOSTto confirm which socket the CLI is targeting. - Never “fix” it with
chmod 666 /var/run/docker.sock.
Before you start
You need a Linux host with Docker Engine installed from Docker’s packages (docker-ce), systemd, and sudo rights. Commands below were written against Docker Engine 27/28 behaviour as documented; the Docker 29 wording above was reproduced with a 29.7 client pointed at a socket the user could not open.
On macOS and Windows, Docker Desktop puts the socket in your home directory (for example ~/.docker/run/docker.sock on a Mac) and it already belongs to you, so this exact error is rare there. On Desktop the usual cause is a stopped VM or the wrong context, both covered below.
Why it happens
The docker command is only an HTTP client. Every command (ps, run, build) becomes an API request to dockerd, which by default listens on the Unix socket /var/run/docker.sock (/var/run is a symlink to /run). A Unix socket is a file, and connecting to it requires write permission on that file.
Docker’s packages create the socket through the docker.socket systemd unit with SocketUser=root, SocketGroup=docker and SocketMode=0660. So exactly two kinds of users can connect: root, and members of the docker group. Everyone else gets EACCES from connect(), which the CLI reports as “permission denied”.
Two details trip people up:
- Group membership is read at login.
usermodedits/etc/group, but a running process keeps the supplementary groups it started with. Your current shell, your SSH session, a VS Code remote server or a Jenkins agent all keep the old list until they are restarted. sudo dockeruses a different setup. It runs as root, reads root’s~/.docker/config.jsonand contexts, and if you used it before, it may have left root-owned files in your own~/.dockerdirectory.
Why the docker group is root in disguise
Anyone who can talk to the Docker API can ask the daemon (which runs as root) to do root things for them:
docker run --rm -it -v /:/host alpine chroot /host shThat command gives a root shell on the host filesystem, no password asked. The Docker docs state it directly: the docker group grants root-level privileges. The same applies to any container that has /var/run/docker.sock mounted into it (CI runners, reverse proxies that watch containers): that container effectively has root on the host.
The other message: daemon not running
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?This appears when the socket path exists but nothing accepts connections, for example after dockerd crashed or Docker Desktop was quit. If the socket file is missing altogether, the Docker 29 CLI says failed to connect to the docker API at unix:///...; check if the path is correct and if the daemon is running. Group changes do not help with either; you need a running daemon or the right socket.
Step-by-step walkthrough
Step 1: Find out which socket the CLI is using
docker context ls
echo "DOCKER_HOST=${DOCKER_HOST:-unset}"On a machine with both Docker Engine and Docker Desktop, output looks like this (the * marks the current context):
NAME DESCRIPTION DOCKER ENDPOINT
default Current DOCKER_HOST based configuration unix:///var/run/docker.sock
desktop-linux * Docker Desktop unix:///home/dev/.docker/desktop/docker.sockIf the endpoint is not the daemon you meant, switch with docker context use default (system Engine) or docker context use desktop-linux. A set DOCKER_HOST environment variable overrides the selected context, so unset it if it points somewhere stale.
Step 2: Check the daemon and the socket
systemctl status docker.socket docker.service --no-pager
ls -l /var/run/docker.sockHealthy output for the socket:
srw-rw---- 1 root docker 0 Oct 4 09:12 /var/run/docker.sockIf docker.service is inactive, start it with sudo systemctl enable --now docker. If the socket’s group is root instead of docker, the docker group did not exist when the package was installed; create it with sudo groupadd docker and restart docker.socket.
Step 3: Compare your session’s groups with the database
id -nG # groups of this shell
getent group docker # members recorded in /etc/groupIf getent lists your user but id -nG does not include docker, the change is made but your session predates it. If neither shows it, you were never added.
Step 4: Add the user and refresh the session
sudo groupadd -f docker
sudo usermod -aG docker "$USER"
newgrp docker # applies to this terminal only
docker run --rm hello-worldThe -a matters: usermod -G docker without -a replaces all your supplementary groups, which can lock you out of sudo. newgrp starts a subshell with the new group, so other terminals still need a fresh login. A full logout (or reboot on desktops where the session lingers) applies it everywhere.
If you ran sudo docker earlier and now see WARNING: Error loading config file: ... permission denied, fix ownership: sudo chown -R "$USER":"$USER" ~/.docker.
Step 5: On shared hosts, choose rootless Docker instead
When giving a user root is not acceptable (shared build servers, multi-tenant VMs), run the daemon as that user:
dockerd-rootless-setuptool.sh install
docker context use rootless
docker info --format '{{.SecurityOptions}}' # includes name=rootlessThe rootless daemon’s socket lives at $XDG_RUNTIME_DIR/docker.sock (typically /run/user/1000/docker.sock), owned by that user, and a container breakout only gains that user’s privileges. The trade-offs: binding host ports below 1024 needs extra configuration, and some networking and resource-limit features depend on cgroup v2 and systemd delegation.
Worked scenario
A team installs Docker on a Jenkins agent and adds the service account:
sudo usermod -aG docker jenkinsBuilds keep failing with the permission-denied message. An engineer SSHes in, runs sudo -u jenkins docker ps, and it works, which makes the failure look random.
Diagnosis: sudo -u jenkins starts a new process that reads the current group database, so it sees docker. The Jenkins agent itself was started before the change:
pid=$(systemctl show -p MainPID --value jenkins)
grep Groups /proc/$pid/status # numeric GIDs: the docker GID is absent
getent group docker # docker:x:998:jenkinsThe fix is to restart the service so it picks up the new groups:
sudo systemctl restart jenkins
grep Groups /proc/$(systemctl show -p MainPID --value jenkins)/status # now includes 998The team also records the decision: this agent is dedicated to CI and treated as a root-equivalent machine, so no other users get shell access to it.
Common mistake
The fix most often pasted into forums is:
sudo chmod 666 /var/run/docker.sockIt “works” because every local user, every compromised web app running as www-data and every container that can see the socket now has root-equivalent access to the host. It also does not last: systemd recreates the socket with 0660 on the next restart, so the error returns and someone runs the command again.
Other tempting shortcuts:
- Prefixing everything with
sudo. It hides the problem, leaves root-owned files in~/.docker, and stores registry credentials under root’s config instead of yours. - Running
usermod -G dockerwithout-a. It strips your other groups, includingsudoorwheel. - Assuming the group change failed because the same terminal still errors. The session is stale, not the configuration.
Verify the behavior
In a fresh login session:
id -nG | tr ' ' '\n' | grep -x docker
docker version --format 'client {{.Client.Version}} / server {{.Server.Version}}'
docker run --rm hello-world | head -n 2Expected: the first command prints docker, the second prints both a client and a server version (a missing server section means the daemon is still unreachable), and the third prints a blank line followed by Hello from Docker!. For a rootless setup, docker context show should print rootless.
Interview exercise
“A developer asks you to add them to the docker group on a shared Linux build server so they can stop typing sudo. What do you tell them, and what would you set up instead?”
Answer and reasoning
Explain the mechanism first: the CLI talks to a root-owned daemon over a socket that only root and the docker group may open. Anyone who can use that API can start a container that bind-mounts / or runs --privileged, so group membership is root access in all but name, and it bypasses sudo logging and policy.
On a shared server I would not add the user. Options, in order of preference: rootless Docker per user, so each person’s daemon runs with their own privileges and socket; or a CI system that builds in isolated runners (ephemeral VMs) rather than sharing one daemon. If the machine is a single-purpose box where the user already has sudo, adding them to the group changes little, and I would say that explicitly. Mention that the change only applies to new login sessions, which explains why “it didn’t work” right after usermod.
Continue learning
- Docker interview questions
- Docker MCQs
- Docker nonroot users and file permissions
- Docker container exits immediately: how to debug it
- Docker ‘Bind for 0.0.0.0:8080 failed: port is already allocated’
- Official: Linux post-installation steps, rootless mode, Docker daemon attack surface and Docker contexts