Ch. 13 · Docker

Docker permission denied on /var/run/docker.sock: Fixes and Risks

Fix 'permission denied while trying to connect to the Docker daemon socket': docker group, re-login, rootless mode, contexts and a dead daemon.

~8 min readbeginnerupdated Oct 4, 2026

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 denied
Text

The 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 be srw-rw---- owned by root docker.
  • Run id -nG and check whether docker is in the list for your current shell.
  • If not: sudo usermod -aG docker $USER, then log out and back in (or run newgrp docker in 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 ls and echo $DOCKER_HOST to 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:

  1. Group membership is read at login. usermod edits /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.
  2. sudo docker uses a different setup. It runs as root, reads root’s ~/.docker/config.json and contexts, and if you used it before, it may have left root-owned files in your own ~/.docker directory.

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 sh
Terminal

That 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?
Text

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}"
Terminal

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.sock
Text

If 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.sock
Terminal

Healthy output for the socket:

srw-rw---- 1 root docker 0 Oct  4 09:12 /var/run/docker.sock
Text

If 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/group
Terminal

If 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-world
Terminal

The -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=rootless
Terminal

The 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 jenkins
Terminal

Builds 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:jenkins
Terminal

The 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 998
Terminal

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

It “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 docker without -a. It strips your other groups, including sudo or wheel.
  • 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 2
Terminal

Expected: 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

More in Docker

read ✓Docker · hard

Docker Distroless and Minimal Images

Ship images without a shell or package manager, copy only the runtime, and accept the debugging trade-off.

~2 min readread →
read ✓Docker · hard

Docker Image Vulnerability Scanning

Scan images for known CVEs in CI, set a policy that blocks the build, and rebuild when base images are patched.

~2 min readread →
esc