Files
EZ-Homelab/wiki/service-docs/docker-proxy.md
kelinfoxy ef55974b50 Wiki major update
updated with recent documentation
2026-01-21 19:18:39 -05:00

16 KiB

Docker Socket Proxy - Secure Docker Socket Access

Table of Contents

Overview

Category: Infrastructure Security
Docker Image: tecnativa/docker-socket-proxy
Default Stack: infrastructure.yml
Web UI: None (proxy service)
Port: 2375 (internal only)
Purpose: Secure access layer for Docker socket

What is Docker Socket Proxy?

Docker Socket Proxy is a security-focused proxy that sits between Docker management tools and the Docker socket. It provides fine-grained access control to Docker API endpoints, allowing you to grant specific permissions rather than full Docker socket access.

Key Features

  • Granular Permissions: Control which Docker API endpoints are accessible
  • Security Layer: Prevents full root access to host
  • Read/Write Control: Separate read-only and write permissions
  • API Filtering: Whitelist specific Docker API calls
  • No Authentication: Relies on network isolation
  • Lightweight: Minimal resource usage
  • HAProxy Based: Stable, proven technology
  • Zero Config: Works out of the box with sensible defaults

Why Use Docker Socket Proxy?

The Problem

Direct Docker socket access (/var/run/docker.sock) grants root-equivalent access to the host:

# DANGEROUS: Full access to Docker = root on host
traefik:
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock

Risks:

  • Container can access all other containers
  • Can mount host filesystem
  • Can escape container isolation
  • Can compromise entire system

The Solution

Docker Socket Proxy provides controlled access:

# SAFER: Limited access via proxy
traefik:
  environment:
    - DOCKER_HOST=tcp://docker-proxy:2375
# No direct socket mount!

docker-proxy:
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro
  environment:
    - CONTAINERS=1  # Allow container list
    - SERVICES=1    # Allow service list
    - TASKS=0       # Deny task access

Benefits:

  1. Principle of Least Privilege: Only grant necessary permissions
  2. Reduced Attack Surface: Limit what compromised container can do
  3. Audit Trail: Centralized access point
  4. Network Isolation: Proxy can be on separate network
  5. Read-Only Socket: Proxy uses read-only mount

How It Works

Management Tool (Traefik/Portainer/Dockge)
     ↓
TCP Connection to docker-proxy:2375
     ↓
Docker Socket Proxy (HAProxy)
     ├─ Check permissions
     ├─ Filter allowed endpoints
     └─ Forward or block request
     ↓
Docker Socket (/var/run/docker.sock)
     ↓
Docker Engine

Request Flow

  1. Tool makes API request: "List containers"
  2. Connects to proxy: tcp://docker-proxy:2375
  3. Proxy checks permissions: Is CONTAINERS=1?
  4. If allowed: Forward to Docker socket
  5. If denied: Return 403 Forbidden
  6. Response returned: To requesting tool

Permission Model

Environment variables control access:

  • CONTAINERS=1 → Allow container operations
  • IMAGES=1 → Allow image operations
  • NETWORKS=1 → Allow network operations
  • VOLUMES=1 → Allow volume operations
  • SERVICES=1 → Allow swarm service operations
  • TASKS=1 → Allow swarm task operations
  • SECRETS=1 → Allow secret operations
  • POST=0 → Deny all write operations (read-only)

Configuration in AI-Homelab

Directory Structure

# No persistent storage needed
# All configuration via environment variables

Environment Variables

# Core Docker API endpoints
CONTAINERS=1    # Container list, inspect, logs, stats
SERVICES=1      # Service management (for Swarm)
TASKS=1         # Task management (for Swarm)
NETWORKS=1      # Network operations
VOLUMES=1       # Volume operations
IMAGES=1        # Image list, pull, push
INFO=1          # Docker info, version
EVENTS=1        # Docker events stream
PING=1          # Health check

# Write operations (set to 0 for read-only)
POST=1          # Create operations
BUILD=0         # Image build (usually not needed)
COMMIT=0        # Container commit
CONFIGS=0       # Config management
DISTRIBUTION=0  # Registry operations
EXEC=0          # Execute in container (dangerous)
SECRETS=0       # Secret management (Swarm)
SESSION=0       # Not commonly used
SWARM=0         # Swarm management

# Security
LOG_LEVEL=info  # Logging verbosity

Official Resources

Educational Resources

Videos

Articles & Guides

Concepts to Learn

  • Unix Socket: Inter-process communication file
  • Docker API: RESTful API for Docker operations
  • TCP Socket: Network socket for remote access
  • HAProxy: Load balancer and proxy
  • Least Privilege: Minimal permissions principle
  • Attack Surface: Potential vulnerability points
  • Container Escape: Breaking out of container isolation

Docker Configuration

Complete Service Definition

docker-proxy:
  image: tecnativa/docker-socket-proxy:latest
  container_name: docker-proxy
  restart: unless-stopped
  privileged: true  # Required for socket access
  networks:
    - docker-proxy-network  # Isolated network
  ports:
    - "2375:2375"  # Only expose internally
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro  # Read-only!
  environment:
    # Core permissions (what Traefik/Portainer need)
    - CONTAINERS=1
    - SERVICES=1
    - NETWORKS=1
    - IMAGES=1
    - INFO=1
    - EVENTS=1
    - PING=1
    
    # Write operations (enable as needed)
    - POST=1
    
    # Deny dangerous operations
    - BUILD=0
    - COMMIT=0
    - EXEC=0
    - SECRETS=0
    
    # Logging
    - LOG_LEVEL=info

networks:
  docker-proxy-network:
    internal: true  # No external access

Connecting Services to Proxy

Traefik Configuration:

traefik:
  networks:
    - traefik-network
    - docker-proxy-network
  environment:
    - DOCKER_HOST=tcp://docker-proxy:2375
  # NO volumes for Docker socket!
  # volumes:
  #   - /var/run/docker.sock:/var/run/docker.sock  # REMOVE THIS

Portainer Configuration:

portainer:
  networks:
    - traefik-network
    - docker-proxy-network
  environment:
    - AGENT_HOST=docker-proxy
  # NO volumes for Docker socket!

Dockge Configuration:

dockge:
  networks:
    - traefik-network
    - docker-proxy-network
  environment:
    - DOCKER_HOST=tcp://docker-proxy:2375
  # NO volumes for Docker socket!

Access Control

Traefik Minimal Permissions

Traefik only needs to read container labels:

docker-proxy:
  environment:
    - CONTAINERS=1  # Read container info
    - SERVICES=1    # Read services
    - NETWORKS=1    # Read networks
    - INFO=1        # Docker info
    - EVENTS=1      # Watch for changes
    - POST=0        # No write operations

Portainer Full Permissions

Portainer needs more access for management:

docker-proxy:
  environment:
    - CONTAINERS=1
    - SERVICES=1
    - TASKS=1
    - NETWORKS=1
    - VOLUMES=1
    - IMAGES=1
    - INFO=1
    - EVENTS=1
    - POST=1        # Create/update
    - PING=1

Watchtower Minimal Permissions

Watchtower needs to pull images and recreate containers:

docker-proxy:
  environment:
    - CONTAINERS=1
    - IMAGES=1
    - INFO=1
    - POST=1        # Create operations

Read-Only Mode

For monitoring tools (Glances, Dozzle):

docker-proxy:
  environment:
    - CONTAINERS=1
    - SERVICES=1
    - TASKS=1
    - NETWORKS=1
    - VOLUMES=1
    - IMAGES=1
    - INFO=1
    - EVENTS=1
    - POST=0        # No writes
    - BUILD=0
    - COMMIT=0
    - EXEC=0

Advanced Topics

Multiple Proxy Instances

Run separate proxies for different permission levels:

docker-proxy-read (for monitoring tools):

docker-proxy-read:
  image: tecnativa/docker-socket-proxy
  environment:
    - CONTAINERS=1
    - IMAGES=1
    - INFO=1
    - POST=0  # Read-only
  networks:
    - monitoring-network

docker-proxy-write (for management tools):

docker-proxy-write:
  image: tecnativa/docker-socket-proxy
  environment:
    - CONTAINERS=1
    - IMAGES=1
    - NETWORKS=1
    - VOLUMES=1
    - POST=1  # Read-write
  networks:
    - management-network

Custom HAProxy Configuration

For advanced filtering, mount custom config:

docker-proxy:
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro
    - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro

Custom haproxy.cfg:

global
    log stdout format raw local0

defaults
    log global
    mode http
    timeout connect 5s
    timeout client 30s
    timeout server 30s

frontend docker
    bind :2375
    default_backend docker_backend

backend docker_backend
    server docker unix@/var/run/docker.sock
    
    # Custom ACLs
    acl containers_req path_beg /containers
    acl images_req path_beg /images
    
    # Only allow specific endpoints
    http-request deny unless containers_req or images_req

Logging and Monitoring

docker-proxy:
  environment:
    - LOG_LEVEL=debug  # More verbose logging
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"

Monitor access:

# View proxy logs
docker logs -f docker-proxy

# See what endpoints are being accessed
docker logs docker-proxy | grep -E "GET|POST|PUT|DELETE"

Network Isolation

networks:
  docker-proxy-network:
    driver: bridge
    internal: true  # No internet access
    ipam:
      config:
        - subnet: 172.25.0.0/16

Only allow specific services:

services:
  traefik:
    networks:
      docker-proxy-network:
        ipv4_address: 172.25.0.2
  
  portainer:
    networks:
      docker-proxy-network:
        ipv4_address: 172.25.0.3

Troubleshooting

Services Can't Connect to Docker

# Check if proxy is running
docker ps | grep docker-proxy

# Test proxy connectivity
docker exec traefik wget -qO- http://docker-proxy:2375/version

# Check networks
docker network inspect docker-proxy-network

# Verify service is on proxy network
docker inspect traefik | grep -A10 Networks

Permission Denied Errors

# Check proxy logs
docker logs docker-proxy

# Example error: "POST /containers/create 403"
# Solution: Add POST=1 to docker-proxy environment

# Check which endpoint is being blocked
docker logs docker-proxy | grep 403

# Enable required permission
# If /images/create is blocked, add IMAGES=1

Traefik Not Discovering Services

# Ensure these are enabled:
docker-proxy:
  environment:
    - CONTAINERS=1
    - SERVICES=1
    - EVENTS=1  # Critical for auto-discovery

# Check if Traefik is using proxy
docker exec traefik env | grep DOCKER_HOST

# Test manually
docker exec traefik wget -qO- http://docker-proxy:2375/containers/json

Portainer Shows "Cannot connect to Docker"

# Portainer needs more permissions
docker-proxy:
  environment:
    - CONTAINERS=1
    - SERVICES=1
    - TASKS=1
    - NETWORKS=1
    - VOLUMES=1
    - IMAGES=1
    - POST=1

# In Portainer settings:
# Environment URL: tcp://docker-proxy:2375
# Not: unix:///var/run/docker.sock

Watchtower Not Updating Containers

# Watchtower needs write access
docker-proxy:
  environment:
    - CONTAINERS=1
    - IMAGES=1
    - POST=1  # Required for creating containers

# Check Watchtower logs
docker logs watchtower

High Memory/CPU Usage

# Check proxy stats
docker stats docker-proxy

# Should be minimal (~10MB RAM, <1% CPU)
# If high, check for connection leaks

# Restart proxy
docker restart docker-proxy

# Check for excessive requests
docker logs docker-proxy | wc -l

Security Best Practices

  1. Read-Only Socket: Always mount socket as :ro
  2. Minimal Permissions: Only enable what's needed
  3. Network Isolation: Use internal network
  4. No Public Exposure: Never expose port 2375 to internet
  5. Separate Proxies: Different proxies for different trust levels
  6. Monitor Access: Review logs regularly
  7. Disable Exec: Never enable EXEC unless absolutely necessary
  8. Regular Updates: Keep proxy image updated
  9. Principle of Least Privilege: Start with nothing, add as needed
  10. Testing: Test permissions in development first

Migration Guide

Converting from Direct Socket Access

Before (insecure):

traefik:
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock

After (secure):

  1. Add docker-proxy:
docker-proxy:
  image: tecnativa/docker-socket-proxy
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro
  environment:
    - CONTAINERS=1
    - SERVICES=1
    - NETWORKS=1
    - EVENTS=1
  networks:
    - docker-proxy-network
  1. Update service:
traefik:
  environment:
    - DOCKER_HOST=tcp://docker-proxy:2375
  networks:
    - traefik-network
    - docker-proxy-network
  # Remove socket volume!
  1. Create network:
networks:
  docker-proxy-network:
    internal: true
  1. Test:
docker compose up -d
docker logs traefik  # Check for errors

Performance Impact

Overhead:

  • Latency: ~1-2ms per request
  • Memory: ~10-20MB
  • CPU: <1%

Minimal impact because:

  • Docker API calls are infrequent
  • Proxy is extremely lightweight
  • HAProxy is optimized for performance

Benchmark:

# Direct socket
time docker ps
# ~0.05s

# Via proxy
time docker -H tcp://docker-proxy:2375 ps
# ~0.06s

# Negligible difference for management operations

Summary

Docker Socket Proxy is a critical security component that:

  • Provides granular access control to Docker API
  • Prevents root-equivalent access from containers
  • Uses principle of least privilege
  • Adds minimal overhead
  • Simple to configure and maintain

Essential for:

  • Production environments
  • Multi-user setups
  • Security-conscious homelabs
  • Compliance requirements
  • Defense in depth strategy

Implementation Priority:

  1. Deploy docker-proxy with minimal permissions
  2. Update Traefik to use proxy (most critical)
  3. Update Portainer to use proxy
  4. Update other management tools
  5. Remove all direct socket mounts
  6. Test thoroughly
  7. Monitor logs

Remember:

  • Direct socket access = root on host
  • Always use read-only socket mount in proxy
  • Start with restrictive permissions
  • Add permissions only as needed
  • Use separate proxies for different trust levels
  • Never expose proxy to internet
  • Monitor access logs
  • Essential security layer for homelab