1. Introduction

Docker is an open-source platform for developers that packages applications into standalone units called containers. These containers bundle the application code with all its dependencies (libraries, settings) for consistent running across environments. On the other hand, Docker Compose simplifies working with applications that require multiple containers. It enables us to define and run these applications using a single configuration file, usually docker-compose.yml.

Docker Compose relies on Docker for its core functionality. It provides a higher-level abstraction on top of Docker, simplifying the management of complex applications with multiple interacting containers. It leverages Docker’s functionality for building, running, and managing the individual containers that make up the application.

However, scenarios arise where Docker Compose is unable to connect to Docker because of issues such as the shutdown of the Docker daemon. These result in an error, as Docker Compose needs Docker to function.

In this tutorial, we’ll explore in detail many possible ways to troubleshoot connection issues between Docker and Docker Compose.

2. Understanding the Connection Issues

Docker Compose relies on Docker to function, and connection failures between them can prevent the composed application from running. These connection failures arise for many reasons:

  • Docker daemon is not running
  • Docker Socket misconfiguration
  • Outdated Docker Compose versions
  • Permission issues for Docker or Docker socket

To demonstrate this connection issue, let’s set up a single YAML file named docker-compose.yml that defines a Web service:

version: '3'
services:
  web:
    image: nginx
    ports:
      - "80"
    volumes:
      - var/www/html

The above configuration uses Docker Compose file format version 3 and defines a single service using the official nginx image. It exposes the container’s port 80 to the host machine’s port 80, enabling us to access the Web server running inside the container. Additionally, it persists the website content from the host directory /var/www/html to the container’s document root for easy management.

Next, let’s create and start this service with docker-compose:

$ docker-compose up docker-compose.yml

However, on execution, we face an error:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 700, in urlopen
    httplib_response = self._make_request(
    ...
  File "/usr/lib/python3/dist-packages/docker/transport/unixconn.py", line 30, in connect
    sock.connect(self.unix_socket)
PermissionError: [Errno 13] Permission denied

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
...
  File "/usr/lib/python3/dist-packages/docker/transport/unixconn.py", line 30, in connect
    sock.connect(self.unix_socket)
urllib3.exceptions.ProtocolError: ('Connection aborted.', PermissionError(13, 'Permission denied'))

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
...
requests.exceptions.ConnectionError: ('Connection aborted.', PermissionError(13, 'Permission denied'))

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
...
  File "/usr/lib/python3/dist-packages/docker/api/client.py", line 221, in _retrieve_server_version
    raise DockerException(
docker.errors.DockerException: Error while fetching server API version: ('Connection aborted.', PermissionError(13, 'Permission denied'))

As seen above, the core error is PermissionError: [Errno 13] Permission denied. Therefore, this means docker-compose lacks the necessary permissions or prerequisites to connect to the Docker daemon. The error originates from the docker Python library, which attempts to connect to the Docker daemon but gets denied access.

Hence, in an attempt to troubleshoot and fix this error, we explore various possible solutions:

  • verify the Docker daemon status
  • check version compatibility
  • update permissions and ownership
  • export environment to connect to Docker (macOS and Windows only)

However, each of the mentioned solutions above applies to most Docker installations independent of the operating system.

3. Verifying the Docker Daemon Status

The Docker daemon, often referred to as dockerd, is a persistent background process that manages Docker objects such as images, containers, networks, and volumes. Additionally, it listens for Docker API requests and handles all container lifecycle operations.

On most modern Linux distributions using systems, the Docker daemon is automatically configured to start after installation and on system boot. For Windows and macOS, users must manually launch Docker Desktop for the first use. Subsequently, it can be configured for automatic startup based on their preferences.

The Docker daemon can be manually started on Ubuntu and other Debian-based operating systems using systemctl:

$ sudo systemctl enable docker
$ sudo systemctl start docker

Therefore, to verify if the Docker daemon is running, the OS-independent method is using the docker info command:

$ docker info
Client: Docker Engine - Community
 Version:    27.0.3
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.15.1
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.28.1
    Path:     /usr/libexec/docker/cli-plugins/docker-compose
...

This command is part of the Docker CLI (Command Line Interface). It maintains consistency across Docker installations on various operating systems, including Linux, macOS, and Windows. As long as Docker is properly installed and running, this command works similarly regardless of the underlying OS. Hence, it verifies that the Docker daemon is running successfully.

Alternatively, on Linux machines, the daemon status can be confirmed using systemctl:

$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2024-07-19 06:15:44 UTC; 6min ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 869 (dockerd)
      Tasks: 9
     Memory: 101.5M
        CPU: 852ms
     CGroup: /system.slice/docker.service
             └─869 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
]: Started Docker Application Container Engine.

The above command checks the status of the Docker daemon. In the command output, we can see Active: active (running), which means that the Docker daemon is running.

4. Checking Version Compatibilities

The unable to connect to Docker error can also arise from version compatibility issues between Docker Compose, the Docker Engine, and the version specified in the composed YAML file.

4.1. File Format Versions

Different Docker Engine versions require specific Compose file formats for compatibility. If the Compose file specifies a version that’s incompatible with the Docker Engine, it can lead to connection issues.

The Compose file format comes in three versions:

  • Version 1 (Legacy): This is the original format and doesn’t require a specific version declaration in the YAML file. It’s omitted by default.
  • Version 2.x: This version uses a version 2 or version 2.1 key at the beginning of the YAML file.
  • Version 3.x (recommended): This is the latest and recommended format, designed for compatibility with both Compose and Docker Engine’s swarm mode. Similar to version 2.x, it uses a version 3 or version 3.1 key at the root of the YAML file.

We can check the Docker engine version by running the docker –version command.

$ docker --version
Docker version 27.0.3, build 7d4bcd8

Here, the command output provides the Docker engine version information.

4.2. File Format Compatibility

We can map the Compose file format versions to the supported Docker engine version releases:

Compose File Format

Docker Engine Release

3.8

19.03.0+

3.7

18.06.0+

3.6

18.02.0+

3.5

17.12.0+

3.4

17.09.0+

3.3

17.06.0+

3.2

17.04.0+

3.1

1.13.1+

3.0

1.13.0+

2.4

17.12.0+

2.3

17.06.0+

2.2

1.13.0+

2.1

1.12.0+

2.0

1.10.0+

1.0

1.9.1.+

Hence, it’s important to use compatible versions, as some Compose file features require specific Docker Engine capabilities. If these aren’t available, a connection error might manifest.

In addition to compatibility issues between Docker Compose file format versions and the Docker engine, there are also compatibility issues between Docker Compose versions and the Docker Engine. However, using an updated version of both Docker Compose and Docker Engine would fix this issue. Additionally, the Docker Compose release notes state the supported Docker Engine versions for each Docker Compose release.

5. Permission and Ownership Issues

Permission and ownership issues are the primary causes of the unable to connect to connect to Docker error. These issues can arise from the fact that the user running the docker-compose command may not have the necessary permissions to interact with Docker or doesn’t have read and write permissions for the Docker socket.

We can resolve this issue in various ways:

  • using sudo
  • manage Docker as a non-root user
  • update socket permissions

Notably, each method can pose a security risk. Hence, it’s important to use the solution best suited for the working environment.

5.1. Using sudo

The sudo command enables users to execute commands with the privileges of the system’s administrative account, typically the root user. Using it with the docker-compose might solve the connection issue:

$ sudo docker-compose up -d
Creating network "vagrant_default" with the default driver
Creating vagrant_web_1 ... done

The output above indicates the command works without any connection errors using sudo privileges. Additionally, the -d flag indicates the web service defined in the YAML file is running in the background. We can check the list of running containers using docker ps command:

$ sudo docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                                     NAMES
24dc6df58e98   nginx     "/docker-entrypoint.…"   10 seconds ago   Up 10 seconds   0.0.0.0:32770->80/tcp, :::32770->80/tcp   vagrant_web_1

While using sudo with docker-compose might seem like a quick fix, it can mask underlying configuration problems that require proper solutions.

5.2. Manage Docker as a Non-root User

To avoid using sudo with Docker commands, Docker recommends adding the current user to the docker group.

Most Linux distributions automatically create the docker group during installation. However, we can also create it manually using the groupadd command:

$ sudo groupadd docker

On execution, let’s add the current user to the docker group:

$ sudo usermod -aG docker $USER

After adding the current user to the docker group, we need to either log in to a new session or use the newgrp command for the changes to take effect:

$ newgrp docker

Hence, with these updated changes, we can now run docker-compose without sudo:

$ docker-compose up -d
Creating network "vagrant_default" with the default driver
Creating vagrant_web_1 ... done

Thus, the output indicates that docker-compose has been executed successfully.

5.3. Update Socket Permissions

The Docker socket file resides at /var/run/docker.sock. For security purposes, only the root user and members of the docker group have read and write permissions to this file. All other users are denied any access. This can be confirmed using the ls command:

$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 Jul 19 15:07 /var/run/docker.sock

The command output verifies that the root user owns the Docker socket file and belongs to the docker group.

Hence, to permit others to access the socket, let’s update the file permission with chmod:

$ sudo chmod 666 /var/run/docker.sock

Again, let’s confirm the updated permissions with the ls command:

$ ls -l /var/run/docker.sock
srw-rw-rw- 1 root docker 0 Jul 19 15:07 /var/run/docker.sock

The output indicates not only the root and docker groups have read and write access but also others. Hence, we can run docker-compose:

$ docker-compose up -d
Creating network "vagrant_default" with the default driver
Creating vagrant_web_1 ... done

Therefore, this resolves the connection error but violates the principle of least privilege in system security, as every user now has access.

6. Export Environment to Connect to Docker (Older macOS and Windows Systems)

This solution is specifically for users of Docker Toolbox on Windows or macOS, which is an older way of running Docker on these operating systems.

To begin, let’s launch the Docker Quickstart Terminal and restart the Docker host VM:

$ docker-machine restart default

This command restarts the Docker host VM, which can resolve issues with the Docker daemon.

Then, we set up environment variables in the current shell session:

$ eval $(docker-machine env default)

The above command configures the shell to communicate with the Docker daemon running in the VM. The variables set includes DOCKER_HOST, DOCKER_CERT_PATH, and similar. However, running the command becomes repetitive because each new terminal session needs the necessary Docker environment variables.

Furthermore, for users of more recent Docker installations (Docker Desktop), this specific solution wouldn’t apply. Modern Docker installations on Windows and macOS don’t typically require these steps, as they integrate more seamlessly with the host operating system.

7. Conclusion

In conclusion, troubleshooting Docker connection issues involves understanding the interplay between the Docker daemon, client permissions, and system configurations. We’ve explored various methods to resolve the Unable to connect to docker error, ranging from checking daemon status, version compatibilities, and restarting services to managing user permissions and Docker socket access.

Notably, each approach, whether adding users to the docker group, adjusting socket permissions, or using sudo, has its own set of implications for security and system management.

So, the best solution depends on the specific environment and security requirements.