# Docker host

URL: https://shellz.dev/docs/install/docker-host

> Record docker exec, docker attach and docker compose exec on a Linux server that runs Docker, without changing your images.

On a Linux server that runs Docker, the standard install also records every interactive session into a container: `docker exec`, `docker attach`, `docker run -it` and `docker compose exec`. Your images and containers don't change.

## Install

Install shellz on the server exactly as described in [Linux server](https://shellz.dev/docs/install/linux.md):

```bash
curl -fsSL https://get.shellz.dev | sudo sh -s -- szt_<join token>
```

shellz finds Docker automatically. Docker isn't restarted and running containers keep running. If you install Docker after shellz, it's picked up within 15 seconds.

Check it:

```bash
sudo /usr/local/bin/shellz status
```

```text title="Output"
  ...
  Containers recording Docker sessions on /run/docker.sock
```

Right after install, the line may end with `(recording; dockerd moves to /run/shellz/dockerd.sock at its next restart)`. That's normal: sessions are already recorded, and Docker switches sockets the next time it restarts. A line starting with `NOT RECORDED` explains what's wrong; see [What isn't covered](https://shellz.dev/docs/install/docker-host.md#what-isnt-covered).

## How it works

Every Docker command talks to the Docker daemon through the API socket, `/run/docker.sock`. shellz moves the daemon to a private socket, `/run/shellz/dockerd.sock`, readable only by root, and serves the original socket path itself with the same owner and permissions. Members of the `docker` group keep working as before.

shellz forwards every Docker API call to the daemon unchanged. When a call opens a session into a container (exec or attach), shellz records the stream in both directions before passing it on.

The change is made with systemd drop-in files (`zz-shellz.conf`) for `docker.socket` and `docker.service`, which are removed when shellz is removed. If someone deletes or replaces the socket, shellz puts it back within a second and raises a `tamper-repaired` alert.

## What gets recorded

| Command | Recorded |
|---|---|
| `docker exec -it <container> bash` | Yes, as a full session |
| `docker exec <container> <command>` (no terminal) | Yes, as a command session |
| `docker attach <container>` | Yes |
| `docker run -it <image>` | Yes, as an attach session |
| `docker compose exec <service> sh` | Yes |
| Docker API calls from tools and SDKs using the socket | Exec and attach calls, yes |
| `docker logs`, `docker ps`, image builds and pulls | No, these aren't sessions |

Each container session shows:

- the container's ID, name and image, and whether it was exec or attach;
- the user inside the container;
- the server account that called Docker, read from the kernel, not from anything the client says;
- a link to the parent session when the `docker` command was run from a recorded SSH session.

As with SSH sessions, input typed at hidden prompts isn't recorded.

## When recording isn't possible

If shellz can't record a new container session, for example because the spool is full while the network is down, the [recording policy](https://shellz.dev/docs/guides/recording-policy.md) decides what happens:

- **Fail closed** and **buffer**: the Docker API returns an error, the client shows `shellz: recording unavailable, container session refused`, and a `session-refused` alert is raised.
- **Fail open**: the session runs.

## What isn't covered

shellz raises an alert when it finds Docker set up in a way it can't record:

| Alert | Cause | Fix |
|---|---|---|
| `docker-tcp-listener` (critical) | The Docker daemon also accepts API calls on a TCP port. Calls over TCP bypass recording. | Remove the `-H tcp://...` flag or the TCP `hosts` entry. |
| `unsupported-container-runtime` (warning) | `hosts` is set in `/etc/docker/daemon.json`, so shellz can't move the socket. | Move the socket settings into the systemd unit and remove `hosts` from `daemon.json`. |
| `unsupported-container-runtime` (warning) | Docker runs without a `docker.service` unit, for example from a snap. | Use Docker's packages for your distribution. |
| `unsupported-container-runtime` (warning) | Rootless Docker, Podman, or containerd tools (`ctr`, `nerdctl`, `crictl`) are in use. | Add shellz [inside the image](https://shellz.dev/docs/install/container-image.md) for those containers. |

> [!WARNING]
> Root on the server can reach containers without Docker, for example with `nsenter` into a container's namespaces. That's still a login on the server, so the outer SSH session records it, but the container session itself isn't recorded separately.

## Removing shellz from a Docker host

When shellz is [removed](https://shellz.dev/docs/guides/updates-and-removal.md#removing-shellz), it deletes its drop-ins and gives the original socket back to Docker. If containers are running, shellz doesn't restart Docker. Instead it prints a note asking you to run this when convenient, so systemd manages the socket again:

```bash
sudo systemctl restart docker.socket docker.service
```
