Hardening
DockTail needs two sockets, and both are powerful. This section says what they grant and how to narrow it. None of it is required, and none of it is covered by DockTail's automated tests; what DockTail needs is taken from its source.
The Docker Socket
Every example mounts /var/run/docker.sock:ro. The :ro flag makes the bind mount read-only, which does not matter for a socket: it does not stop anyone from connecting to it, and a connection gets the full Docker API — start, exec, create privileged containers. Treat access to the Docker socket as root on the host.
DockTail itself only reads from it: it lists and inspects containers and follows container events, and with DockTail Cloud on it also reads engine info and version, one-shot container stats, and log tails. It never creates, starts, stops or execs anything. To make that a guarantee rather than a property of the code, put a read-only proxy in front of the socket.
Read-Only Socket Proxy
tecnativa/docker-socket-proxy allows only the API sections you enable and, with POST=0 (its default), only GET and HEAD requests. DockTail needs CONTAINERS on top of the sections the proxy allows by default (EVENTS, PING, VERSION), and INFO as well when DockTail Cloud is on:
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:latest
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- CONTAINERS=1 # list, inspect, stats, logs
- INFO=1 # DockTail Cloud only: engine ID and host specs
- EVENTS=1 # default; container start/stop/die/restart/oom/health events
- PING=1 # default; API version negotiation
- VERSION=1 # default
- POST=0 # default; read-only
networks:
- docker-api
docktail:
image: ghcr.io/marvinvr/docktail:latest
restart: unless-stopped
depends_on:
- docker-socket-proxy
volumes:
- /var/run/tailscale:/var/run/tailscale
environment:
- DOCKER_HOST=tcp://docker-socket-proxy:2375
- TAILSCALE_OAUTH_CLIENT_ID=${TAILSCALE_OAUTH_CLIENT_ID}
- TAILSCALE_OAUTH_CLIENT_SECRET=${TAILSCALE_OAUTH_CLIENT_SECRET}
networks:
- default # outbound: the Tailscale API and DockTail Cloud
- docker-api
networks:
docker-api:
internal: true
- Keep the proxy on an
internalnetwork that only DockTail joins, and never publish port 2375: anyone who can reach it can read what DockTail reads. DockTail itself also stays on a normal network, since it calls the Tailscale API (and DockTail Cloud) over the internet. - Read-only is not the same as harmless.
CONTAINERS=1allows everyGETunder/containers: inspecting any container (including environment variables that may hold other apps' secrets), reading its logs, and downloading files from it through the archive and export endpoints. - If the proxy closes the event stream, DockTail logs
Docker event stream errorand reconnects after five seconds; the periodic reconcile covers anything that changed in between.
Container Privileges
The image runs DockTail as root. It needs no Linux capabilities for its own work — it talks to Unix sockets (or the proxy above), reads files, and runs the tailscale CLI against tailscaled's socket — so you can drop them all and block privilege escalation:
services:
docktail:
image: ghcr.io/marvinvr/docktail:latest
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
Dropping CAP_DAC_OVERRIDE also makes root subject to ordinary file permission bits. Credential files loaded through *_FILE or FILE__* must then be owned by root or readable by everyone inside the container; Docker Swarm secrets are, by default. A 0600 file owned by your own user, bind-mounted from the host, is not, and DockTail exits at startup when it cannot read it.