Running docker commands from Common Lisp REPLs
Tags: commonlisp, lisp, docker, productivity
Published: 2022-09-13 05:09:13

In this post, we are going to take a look at how we can run docker commands from Common Lisp. Lately I have been exploring Common Lisp development in Docker. So I have to run docker commands from a terminal frequently.

I was basically looking for a way to run docker commands within SLIME itself. Then I came across the [uiop:run-program](https://asdf.common-lisp.dev/uiop.html#UIOP_002fRUN_002dPROGRAM) function from the [Common Lisp Cookbook](https://lispcookbook.github.io/cl-cookbook/os.html#running-external-programs)

`uiop:run-program` fully portably runs a program as a synchronous external subprocess.

So if you want to list all the Docker containers from SLIME or any Lisp REPL, you have to call the `run-program` function like this:

```lisp
(uiop:run-program "docker ps -a" :output t)
```

We need to provide the `:output t` argument  to print the results of the command to the standard output.

The result will be something like:
```
CL-USER> (uiop:run-program "docker ps -a" :output t)
CONTAINER ID   IMAGE               COMMAND                  CREATED         STATUS                  PORTS     NAMES
673b20976166   docker101tutorial   "/docker-entrypoint.…"   19 months ago   Exited (0) 4 days ago             docker-tutorial
NIL
NIL
0
```


# cl-docker

Running docker commands everytime using `uiop:run-program` and passing all the options to print the output to the standard output felt like a time-consuming and non-Lispy way of doing stuff for me.

I wanted to run docker commands in a more natural way using Lisp, so I planned to write a package for the same. The original inspiration for this idea came from another Common Lisp package called [cl-virtualbox](https://github.com/eudoxia0/cl-virtualbox) by [Fernando Borretti](https://github.com/eudoxia0). `cl-virtualbox` is a library that allows you to control VirtualBox from Common Lisp, by calling the `vboxmanage` command.

So our new package called [cl-docker](https://github.com/rajasegar/cl-docker) will  allow us to control Docker from Common Lisp by calling the `docker` command.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cyuguxceszfpyf70m2c5.png)


## Installation
```lisp
(ql:quickload :cl-docker)
```

## Usage

So now, to list out all the Docker containers in your machine, you can just call the below function in your REPL 

```lisp
;; List all docker containers
(docker:ps :a)
```

### Output:
```
"CONTAINER ID   IMAGE               COMMAND                  CREATED         STATUS                    PORTS     NAMES
673b20976166   docker101tutorial   \"/docker-entrypoint.…\"   19 months ago   Exited (0) 16 hours ago             docker-tutorial
"
NIL
0
```

Following is some of the docker commands and their corresponding `cl-docker` functions

|   **docker command**   |       **cl-docker**      |
|:----------------------:|:------------------------:|
| docker ps -a           | (docker:ps :a)           |
| docker images          | (docker:images)          |
| docker build DIRECTORY | (docker:build "myimage") |
| docker rm CONTAINER    | (docker:rm "web")        |
| docker start CONTAINER | (docker:start "web")     |
| docker stop CONTAINER  | (docker:stop "web")      |

The more complete list of commands can be viewed from the project [README](https://github.com/rajasegar/cl-docker)

## Moving away from uiop:run-program
Using `uiop:run-program` to run and manage docker from Common Lisp seems like an easy way to get things done. But it's not a clean and scalable way of doing things, basically we are interfacing with docker using the operating system command line capabilities. There are still issues in capturing the proper output, error information and unnecessary output like `NIL` and `0`, which are actually the function return values and the return codes for the commands you run with `uiop:run-program`. 

So how do we get around these things? The best way to properly manage docker from Common Lisp is to use the [Docker Engine SDKs](https://docs.docker.com/engine/api/sdk/). 

Docker provides an API for interacting with the Docker daemon (called the Docker Engine API), as well as SDKs for Go and Python. At present, there is no pre-built SDK available for Common Lisp.

The SDKs allow you to build and scale Docker apps and solutions quickly and easily. So for Common Lisp, we have to  use the Docker Engine API directly.

The Docker Engine API is a RESTful API accessed by an HTTP client such as wget or curl, or the HTTP library which is part of most modern programming languages.

Using the SDK API to get a list of containers, just like using `docker ps`, we need to send a curl request like this:

```
curl --unix-socket /var/run/docker.sock http://localhost/v1.41/containers/json
```

All we need to figure out is how to replicate this curl request in Common Lisp using an HTTP Client library using unix sockets. That's what I am trying to figure out now. If you have any ideas, please let me know in the comments.


`cl-docker` is still in early stages of development. It may not have covered all the docker commands with all the options that can be provided. So please give a try to the package and let me know for any queries/feedback in the comments section. You can also raise bug fixes, enhancements and feature requests through the Github [issues](https://github.com/rajasegar/cl-docker/issues)