cURL docker through sockets
Tags: docker, curl, api, serverclient
Published: 2022-02-17 08:57:06

_Did you know that the docker daemon exposes a [gRPC](https://grpc.io/) api for communication OR that cURL can send data to unix sockets?_

Let's see how this all works.

### Docker engine
The underlying infrastructure of the docker ecosystem is the [docker engine](https://docs.docker.com/engine/).

It is implemented using a client/server model (much like most unix tools are), and exposes its [API](https://docs.docker.com/engine/api/) over a gRPC interface. As usual in such model, there is usually only one server, and many clients, and the story is no different for docker.

![Docker architecture image](https://docs.docker.com/engine/images/architecture.svg)
_Docker architecture. Image from [https://docs.docker.com](https://docs.docker.com/get-started/overview/#docker-architecture)_

The daemon acts as the **server**, while the CLI which we are all familiar with acts as one of the **clients**. The single point of communication is usually exposed as a unix socket, and communication is carried out via gRPC exchanging data in the form of [protocol buffers](https://developers.google.com/protocol-buffers).

### cURL --unix-socket
It turns out that cURL is not just great for talking to HTTP servers, but it can apparently also do the same via UNIX SOCKETS.

Let's say we wanted to get the docker version currently installed; Normally you would do the following on the command line:

```
docker version
```
Using the api, we do something like

```
curl --silent --unix-socket /run/user/1000/docker.sock http://v1.41/version
```
<sup>_Note the above socket path assumes you are running docker in rootless mode and your user id is `1000`. If running docker as root, the socket is likely to be located at `/var/run/docker.sock`_</sup>

Which gives us:

```
{"Platform":{"Name":"Docker Engine - Community"},"Components":[{"Name":"Engine","Version":"20.10.12","Details":{"ApiVersion":"1.41","Arch":"amd64","BuildTime":"2021-12-13T11:46:12.000000000+00:00","Experimental":"false","GitCommit":"459d0df","GoVersion":"go1.16.12","KernelVersion":"5.13.0-28-generic","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"containerd","Version":"v1.4.12","Details":{"GitCommit":"7b11cfaabd73bb80907dd23182b9347b4245eb5d"}},{"Name":"runc","Version":"1.0.2","Details":{"GitCommit":"v1.0.2-0-g52b36a2d"}},{"Name":"docker-init","Version":"0.19.0","Details":{"GitCommit":"de40ad0"}}],"Version":"20.10.12","ApiVersion":"1.41","MinAPIVersion":"1.12","GitCommit":"459d0df","GoVersion":"go1.16.12","Os":"linux","Arch":"amd64","KernelVersion":"5.13.0-28-generic","BuildTime":"2021-12-13T11:46:12.000000000+00:00"}
```

### How cool is that?!

You can take this even a step further, and expose a local docker daemon, over tcp to your friends over the internet. Here is a quick example which doesn't use cURL, but rather uses [ncat](https://man7.org/linux/man-pages/man1/ncat.1.html):

#### server
```
ncat -kl -p 2376 -c 'ncat -U /run/user/1000/docker.sock'
```

#### client
```
curl http://localhost:2376/version | jq
```
---

One last thing to mention is that the format of the url ex. `http://v1.41/version` is not strict. Any of the following would have given the same result:

- `http:/foo/version`
- `http://dummy/version`
- `http:/./version`

The [recommendation](https://docs.docker.com/engine/api/#versioned-api-and-sdk) is to use the **version of the API** supported by docker as the first part of the url, then the actual commands come later.

---

Sources:

- v1.41 [api specification](https://docs.docker.com/engine/api/v1.41/)
- https://nathanleclaire.com/blog/2015/11/12/using-curl-and-the-unix-socket-to-talk-to-the-docker-api/