Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ coverage.txt

# Docker
Dockerfile
docker-compose.yaml
docker-compose*.yaml
.dockerignore

# Example data
Expand Down
46 changes: 39 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# Copy to .env and uncomment what you need. Every variable below is documented
# in docs/07-reference.md (https://docktail.org/docs/#reference); the defaults
# shown in comments are what DockTail uses when the variable is unset.
#
# Compose reads .env only to fill in ${...} references. A variable reaches
# DockTail only when the service lists it under environment:, as the example
# compose files do for the Tailscale credentials and DOCKTAIL_CLOUD_KEY; add a
# line there for anything else you set here.

# =============================================================================
# Tailscale sidecar (only for docker-compose.sidecar.yaml)
Expand Down Expand Up @@ -74,10 +79,21 @@ TAILSCALE_AUTH_KEY=tskey-auth-xxxxx
# =============================================================================

# Docker daemon socket. Rootless Docker typically uses
# unix:///run/user/<uid>/docker.sock
# unix:///run/user/<uid>/docker.sock; a read-only socket proxy is
# tcp://docker-socket-proxy:2375 (docs/02-security.md). Set it under the
# service's environment: rather than here: Compose itself can also pick up a
# DOCKER_HOST from .env and would then talk to that daemon.
# DOCKER_HOST=unix:///var/run/docker.sock

# Tailscale daemon socket.
# Standard Docker client variables, only needed for a TLS-protected tcp://
# daemon or to pin the API version (negotiated by default).
# DOCKER_CERT_PATH=
# DOCKER_TLS_VERIFY=1
# DOCKER_API_VERSION=

# tailscaled socket probed by the socket-loss check. The bundled tailscale CLI
# always uses /var/run/tailscale/tailscaled.sock, so mount the daemon's socket
# directory at /var/run/tailscale regardless.
# TAILSCALE_SOCKET=/var/run/tailscale/tailscaled.sock

# Exit when the Tailscale socket stays unreachable past the grace period, so the
Expand All @@ -88,20 +104,36 @@ TAILSCALE_AUTH_KEY=tskey-auth-xxxxx
# stay comfortably longer than a normal tailscaled restart.
# SOCKET_LOSS_GRACE_PERIOD=90s

# Logging level: debug, info, warn, or error.
# Logging level for all output, including the cloud module: debug, info, warn,
# or error.
# LOG_LEVEL=info

# Log lines are colored only on a terminal. Any value disables color there too.
# NO_COLOR=1

# =============================================================================
# DockTail Cloud (optional)
# =============================================================================
# Opt-in reporting to cloud.docktail.org. The module is completely inert unless
# DOCKTAIL_CLOUD_KEY is set. See docs/06-cloud.md (https://docktail.org/cloud/).
# Opt-in reporting to cloud.docktail.org, a paid service. The module is
# completely inert unless DOCKTAIL_CLOUD_KEY is set. See docs/06-cloud.md
# (https://docktail.org/docs/#docktail-cloud) and docker-compose.cloud.yaml.

# Workspace key (dtc_...) from the cloud dashboard.
# DOCKTAIL_CLOUD_KEY=

# Log level for the cloud module: debug, info, warn, or error.
# Read but currently has no effect; LOG_LEVEL sets the cloud module's level.
# DOCKTAIL_LOG_LEVEL=info

# How often local-vantage checks run (5s to 5m).
# How often local-vantage checks run (5s to 5m). An invalid value keeps the
# cloud module from starting.
# DOCKTAIL_CHECK_INTERVAL=30s

# Where the host's root filesystem is bind-mounted read-only (- /:/host:ro) to
# report disk usage for every host filesystem. Used only when mounted.
# DOCKTAIL_HOST_ROOT=/host

# Local development only: override the ingest endpoint. Plaintext ws:// is
# accepted for loopback hosts, or elsewhere with DOCKTAIL_CLOUD_ALLOW_INSECURE.
# Never use either in production.
# DOCKTAIL_CLOUD_URL=ws://localhost:8080/v1/agent
# DOCKTAIL_CLOUD_ALLOW_INSECURE=false
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ build-all:
GOOS=darwin GOARCH=amd64 go build -o $(BINARY_NAME)-darwin-amd64 .
GOOS=darwin GOARCH=arm64 go build -o $(BINARY_NAME)-darwin-arm64 .

# Start docker-compose
# Start the development stack (builds from source; see docker-compose.dev.yaml)
up:
docker compose up -d
docker compose -f docker-compose.dev.yaml up -d

# Stop docker-compose
# Stop the development stack
down:
docker compose down
docker compose -f docker-compose.dev.yaml down

# View logs
# View the development stack's DockTail logs
logs:
docker logs -f docktail
docker logs -f dev-docktail
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ docker compose up -d
curl http://myapp.your-tailnet.ts.net
```

This assumes the Docker host is connected to Tailscale and allowed to advertise services. See the full docs for host setup, sidecar setup, rootless Docker, OAuth permissions, ACLs, labels, Funnel, and examples.
This assumes the Docker host is connected to Tailscale and allowed to advertise services. See the full docs for host setup, sidecar setup, rootless Docker, OAuth permissions, ACLs, labels, Funnel, and examples. [`docker-compose.yaml`](docker-compose.yaml) is a ready-to-run version of this setup; for Podman, Synology, Unraid, TrueNAS, macOS, Windows, Swarm and Kubernetes see the [platform guides](docs/02-platforms.md), and for a read-only Docker socket proxy and dropped capabilities see [Hardening](docs/02-security.md).

For Docker secrets or other mounted secret files, set `FILE__TAILSCALE_OAUTH_CLIENT_ID` / `FILE__TAILSCALE_OAUTH_CLIENT_SECRET` or `TAILSCALE_OAUTH_CLIENT_ID_FILE` / `TAILSCALE_OAUTH_CLIENT_SECRET_FILE` to the mounted file paths instead of putting the values directly in the environment.

Expand Down Expand Up @@ -151,6 +151,8 @@ environment:

Without the key the module is completely inert: no connection is opened and DockTail behaves exactly as before. The link is outbound-only and metadata-only — the protocol has no exec, deploy, or shell message types, which you can verify in [`cloud/`](cloud/).

DockTail Cloud is a paid service ([plans and pricing](https://docktail.org/cloud/#pricing)); a new workspace can connect one host as an unmonitored preview before you choose a plan. [`docker-compose.cloud.yaml`](docker-compose.cloud.yaml) is a complete example.

[Explore DockTail Cloud](https://docktail.org/cloud/) · [open the dashboard](https://cloud.docktail.org/login) · [agent setup](docs/06-cloud.md)

## Documentation
Expand Down
51 changes: 51 additions & 0 deletions docker-compose.cloud.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# DockTail with DockTail Cloud reporting, on a Linux host that already runs
# Tailscale. DockTail Cloud is a paid, hosted service:
# https://docktail.org/cloud/ -- agent setup: https://docktail.org/docs/#docktail-cloud
#
# Before you start:
# 1. DockTail works on this host without Cloud (host tagged, tailnet policy
# set up): https://docktail.org/docs/#tailscale-admin-setup
# 2. Tailscale OAuth credentials (or an API key) are configured. Cloud's
# tailnet health check needs them; without them it reports "no Tailscale
# credentials" for this host instead.
# 3. A workspace key (dtc_...) from https://cloud.docktail.org whose
# enrollment window is open.
#
# Usage:
# Put TAILSCALE_OAUTH_CLIENT_ID, TAILSCALE_OAUTH_CLIENT_SECRET and
# DOCKTAIL_CLOUD_KEY in .env (see .env.example), then:
# docker compose -f docker-compose.cloud.yaml up -d
#
# No Tailscale on the host (macOS, Windows, most NAS devices)? Take the
# tailscale service and the tailscale-socket volume from
# docker-compose.sidecar.yaml and mount that volume here instead of
# /var/run/tailscale.

services:
docktail:
image: ghcr.io/marvinvr/docktail:latest
container_name: docktail
# Required: DockTail exits on purpose when it loses the Tailscale socket and
# relies on the restart policy to come back with a fresh mount.
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
# Mount the directory, not the socket file.
- /var/run/tailscale:/var/run/tailscale
# Optional. Reports disk usage for every host filesystem, not just the
# one backing /var/lib/docker. Read-only and used for nothing else.
# - /:/host:ro
environment:
- TAILSCALE_OAUTH_CLIENT_ID=${TAILSCALE_OAUTH_CLIENT_ID}
- TAILSCALE_OAUTH_CLIENT_SECRET=${TAILSCALE_OAUTH_CLIENT_SECRET}
# Enables DockTail Cloud reporting. Without it the module is inert.
- DOCKTAIL_CLOUD_KEY=${DOCKTAIL_CLOUD_KEY:?set DOCKTAIL_CLOUD_KEY in .env}

# Example app. No published ports: DockTail proxies to the container IP.
whoami:
image: traefik/whoami:latest
restart: unless-stopped
labels:
- "docktail.service.enable=true"
- "docktail.service.name=whoami"
- "docktail.service.port=80"
119 changes: 119 additions & 0 deletions docker-compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Development stack: builds DockTail from source next to a handful of labelled
# test containers. Not a deployment example -- for that, see
# docker-compose.yaml (plain) or docker-compose.cloud.yaml (with DockTail Cloud).
#
# Usage:
# make up # docker compose -f docker-compose.dev.yaml up -d
# make logs

services:
# DockTail service
dev-docktail:
build: .
container_name: dev-docktail
restart: unless-stopped
volumes:
# Docker socket for container monitoring
- /var/run/docker.sock:/var/run/docker.sock:ro
# Tailscale socket directory (directory mount survives tailscaled restarts)
- /var/run/tailscale:/var/run/tailscale
environment:
- LOG_LEVEL=debug
- RECONCILE_INTERVAL=60s

# Example: Direct mode (default) - no ports needed!
web1:
build: ./test/nginx-web1
container_name: web1
restart: unless-stopped
# No ports needed - DockTail proxies directly to container IP
labels:
- "docktail.service.enable=true"
- "docktail.service.name=test-web1"
- "docktail.service.port=80"
- "docktail.service.protocol=http"
- "docktail.service.service-port=80"
- "docktail.service.service-protocol=http"

# Example: Direct mode with HTTPS (using smart defaults)
web2:
build: ./test/nginx-web2
container_name: web2
restart: unless-stopped
# No ports needed - DockTail proxies directly to container IP
labels:
- "docktail.service.enable=true"
- "docktail.service.name=test-web2"
- "docktail.service.port=80"
- "docktail.service.service-protocol=https"
# service-port defaults to "443" (based on service-protocol=https)

# Example: Serve + Funnel (both tailnet AND public internet access)
web-public:
build: ./test/nginx-web1
container_name: web-public
restart: unless-stopped
# No ports needed - direct mode handles both serve and funnel
labels:
# Serve configuration (tailnet-only):
- "docktail.service.enable=true"
- "docktail.service.name=public-demo"
- "docktail.service.port=80"
- "docktail.service.service-port=443"

# Funnel configuration (public internet):
- "docktail.funnel.enable=true"
- "docktail.funnel.port=80"

# Example: TCP service with direct mode
test-tcp:
image: nginx:latest
container_name: test-tcp
restart: unless-stopped
# No ports needed
labels:
- "docktail.service.enable=true"
- "docktail.service.name=test-tcp"
- "docktail.service.port=80"
- "docktail.service.protocol=tcp"

# Example: Legacy mode (published ports) - opt out of direct mode
test-legacy:
image: nginx:latest
container_name: test-legacy
restart: unless-stopped
ports:
- "9080:80" # Required when direct=false
labels:
- "docktail.service.enable=true"
- "docktail.service.name=test-legacy"
- "docktail.service.port=80"
- "docktail.service.direct=false" # Use published port instead of container IP

# Example: Custom Docker network
test-custom-network:
image: nginx:latest
container_name: test-custom-network
restart: unless-stopped
networks:
- backend
labels:
- "docktail.service.enable=true"
- "docktail.service.name=test-custom-net"
- "docktail.service.port=80"
- "docktail.service.network=backend" # Specify which network to use

# Example: Host networking mode
test-host-network:
image: nginx:latest
container_name: test-host-network
restart: unless-stopped
network_mode: host
labels:
- "docktail.service.enable=true"
- "docktail.service.name=test-host"
- "docktail.service.port=80"
# Host network uses localhost directly

networks:
backend:
Loading
Loading