# Docker Integration Docker container discovery, connection management, and label-based route configuration. ## Overview The docker package implements Docker container integration, providing shared client connections, container parsing from Docker API responses, label processing for route configuration, and container filtering capabilities. ### Primary consumers - `internal/route/provider` - Creates Docker-based route providers - `internal/idlewatcher` - Container idle detection - Operators - Configure routes via Docker labels ### Non-goals - Docker image building or management - Container lifecycle operations (start/stop) - Volume management - Docker Swarm orchestration ### Stability Stable internal package. Public API consists of client management and container parsing functions. ## Public API ### Exported types ```go type SharedClient struct { *client.Client cfg types.DockerProviderConfig refCount atomic.Int32 closedOn atomic.Int64 key string addr string dial func(ctx context.Context) (net.Conn, error) unique bool } ``` ```go type Container struct { DockerCfg types.DockerProviderConfig Image Image ContainerName string ContainerID string Labels map[string]string ActualLabels map[string]string Mounts []Mount Network string PublicPortMapping map[int]PortSummary PrivatePortMapping map[int]PortSummary Aliases []string IsExcluded bool IsExplicit bool IsHostNetworkMode bool Running bool State string PublicHostname string PrivateHostname string Agent *agentpool.Agent IdlewatcherConfig *IdlewatcherConfig } ``` ### Exported functions ```go func NewClient(cfg types.DockerProviderConfig, unique ...bool) (*SharedClient, error) ``` Creates or returns a Docker client. Reuses existing clients for the same URL. Thread-safe. ```go func Clients() map[string]*SharedClient ``` Returns all currently connected clients. Callers must close returned clients. ```go func FromDocker(c *container.Summary, dockerCfg types.DockerProviderConfig) *types.Container ``` Converts Docker API container summary to internal container type. Parses labels for route configuration. ```go func UpdatePorts(ctx context.Context, c *Container) error ``` Refreshes port mappings from container inspect. ```go func DockerComposeProject(c *Container) string ``` Returns the Docker Compose project name. ```go func DockerComposeService(c *Container) string ``` Returns the Docker Compose service name. ```go func Dependencies(c *Container) []string ``` Returns container dependencies from labels. ```go func IsBlacklisted(c *Container) bool ``` Checks if container should be excluded from routing. ## Architecture ### Core components ```mermaid graph TD A[Docker API] --> B[SharedClient Pool] B --> C{Client Request} C -->|New Client| D[Create Connection] C -->|Existing| E[Increment RefCount] F[Container List] --> G[FromDocker Parser] G --> H[Container Struct] H --> I[Route Builder] J[Container Labels] --> K[Label Parser] K --> L[Route Config] subgraph Client Pool B --> M[clientMap] N[Cleaner Goroutine] end ``` ### Client lifecycle ```mermaid stateDiagram-v2 [*] --> New: NewClient() called New --> Shared: Refcount = 1, stored in pool Shared --> Shared: Same URL, increment refcount Shared --> Idle: Close() called, refcount = 0 Idle --> Closed: 10s timeout elapsed Idle --> Shared: NewClient() for same URL Closed --> [*]: Client closed Unique --> [*]: Close() immediately ``` ### Container parsing flow ```mermaid sequenceDiagram participant Provider participant SharedClient participant DockerAPI participant ContainerParser participant RouteBuilder Provider->>SharedClient: NewClient(cfg) SharedClient->>SharedClient: Check Pool alt Existing Client SharedClient->>SharedClient: Increment RefCount else New Client SharedClient->>DockerAPI: Connect DockerAPI-->>SharedClient: Client end Provider->>SharedClient: ListContainers() SharedClient->>DockerAPI: GET /containers/json DockerAPI-->>SharedClient: Container List SharedClient-->>Provider: Container List loop For Each Container Provider->>ContainerParser: FromDocker() ContainerParser->>ContainerParser: Parse Labels ContainerParser->>ContainerParser: Resolve Hostnames ContainerParser-->>Provider: *Container end Provider->>RouteBuilder: Create Routes RouteBuilder-->>Provider: Routes ``` ### Client pool management The docker package maintains a pool of shared clients: ```go var ( clientMap = make(map[string]*SharedClient, 10) clientMapMu sync.RWMutex ) func initClientCleaner() { cleaner := task.RootTask("docker_clients_cleaner", true) go func() { ticker := time.NewTicker(cleanInterval) for { select { case <-ticker.C: closeTimedOutClients() case <-cleaner.Context().Done(): // Cleanup all clients } } }() } ``` ## Configuration Surface ### Docker provider configuration ```yaml providers: docker: local: ${DOCKER_HOST} remote1: scheme: tcp host: docker1.local port: 2375 remote2: scheme: tls host: docker2.local port: 2375 tls: ca_file: /path/to/ca.pem cert_file: /path/to/cert.pem key_file: /path/to/key.pem ``` ### Route configuration labels Route labels use the format `proxy..` where `` is the route alias (or `*` for wildcard). The base labels apply to all routes. | Label | Description | Example | | ---------------------- | ------------------------------- | ------------------------------- | | `proxy.aliases` | Route aliases (comma-separated) | `proxy.aliases: www,app` | | `proxy.exclude` | Exclude from routing | `proxy.exclude: true` | | `proxy.network` | Docker network | `proxy.network: frontend` | | `proxy..host` | Override hostname | `proxy.app.host: 192.168.1.100` | | `proxy..port` | Target port | `proxy.app.port: 8080` | | `proxy..scheme` | HTTP scheme | `proxy.app.scheme: https` | | `proxy..*` | Any route-specific setting | `proxy.app.no_tls_verify: true` | #### Wildcard alias Use `proxy.*.` to apply settings to all routes: ```yaml labels: proxy.aliases: app1,app2 proxy.*.scheme: https proxy.app1.port: 3000 # overrides wildcard ``` ### Idle watcher labels | Label | Description | Example | | ----------------------- | ------------------------------- | ---------------------------------- | | `proxy.idle_timeout` | Idle timeout duration | `proxy.idle_timeout: 30m` | | `proxy.wake_timeout` | Max time to wait for wake | `proxy.wake_timeout: 10s` | | `proxy.stop_method` | Stop method (pause, stop, kill) | `proxy.stop_method: stop` | | `proxy.stop_signal` | Signal to send (e.g., SIGTERM) | `proxy.stop_signal: SIGTERM` | | `proxy.stop_timeout` | Stop timeout in seconds | `proxy.stop_timeout: 30` | | `proxy.depends_on` | Container dependencies | `proxy.depends_on: database` | | `proxy.start_endpoint` | Optional path restriction | `proxy.start_endpoint: /api/ready` | | `proxy.no_loading_page` | Skip loading page | `proxy.no_loading_page: true` | ### Docker Compose labels Those are created by Docker Compose. | Label | Description | | ------------------------------- | -------------------- | | `com.docker.compose.project` | Compose project name | | `com.docker.compose.service` | Service name | | `com.docker.compose.depends_on` | Dependencies | ## Dependency and Integration Map ### Internal dependencies - `internal/agentpool` - Agent-based Docker host connections - `internal/maxmind` - Container geolocation - `internal/types` - Container and provider types - `internal/task/task.go` - Lifetime management ### External dependencies - `github.com/docker/cli/cli/connhelper` - Connection helpers - `github.com/moby/moby/client` - Docker API client - `github.com/docker/go-connections/nat` - Port parsing ### Integration points ```go // Route provider uses docker for container discovery client, err := docker.NewClient(cfg) containers, err := client.ContainerList(ctx, container.ListOptions{}) for _, c := range containers { container := docker.FromDocker(c, cfg) // Create routes from container } ``` ## Observability ### Logs - Client initialization and cleanup - Connection errors - Container parsing errors ### Metrics No metrics are currently exposed. ## Security Considerations - Docker socket access requires proper permissions - TLS certificates for remote connections - Agent-based connections are authenticated via TLS - Database containers are automatically blacklisted ### Blacklist detection Containers are automatically blacklisted if they: - Mount database directories: - `/var/lib/postgresql/data` - `/var/lib/mysql` - `/var/lib/mongodb` - `/var/lib/mariadb` - `/var/lib/memcached` - `/var/lib/rabbitmq` - Expose database ports: - 5432 (PostgreSQL) - 3306 (MySQL/MariaDB) - 6379 (Redis) - 11211 (Memcached) - 27017 (MongoDB) ## Failure Modes and Recovery | Failure | Behavior | Recovery | | -------------------------- | ---------------------------- | ------------------------ | | Docker socket inaccessible | NewClient returns error | Fix socket permissions | | Remote connection failed | NewClient returns error | Check network/tls config | | Container inspect failed | UpdatePorts returns error | Container may be stopped | | Invalid labels | Container created with error | Fix label syntax | | Agent not found | Panic during client creation | Add agent to pool | ## Performance Characteristics - Client pooling reduces connection overhead - Reference counting prevents premature cleanup - Background cleaner removes idle clients after 10s - O(n) container parsing where n is container count ## Usage Examples ### Creating a Docker client ```go dockerCfg := types.DockerProviderConfig{ URL: "unix:///var/run/docker.sock", } client, err := docker.NewClient(dockerCfg) if err != nil { log.Fatal(err) } defer client.Close() ``` ### Using unique client ```go // Create a unique client that won't be shared client, err := docker.NewClient(cfg, true) if err != nil { log.Fatal(err) } // Remember to close when done client.Close() ``` ### Getting all clients ```go clients := docker.Clients() for host, client := range clients { log.Printf("Connected to: %s", host) } // Use clients... // Close all clients when done for _, client := range clients { client.Close() } ``` ### Parsing containers ```go containers, err := dockerClient.ContainerList(ctx, container.ListOptions{}) for _, c := range containers { container := docker.FromDocker(c, dockerCfg) if container.Errors != nil { log.Printf("Container %s has errors: %v", container.ContainerName, container.Errors) continue } log.Printf("Container: %s, Aliases: %v", container.ContainerName, container.Aliases) } ``` ### Checking if container is blacklisted ```go container := docker.FromDocker(c, dockerCfg) if docker.IsBlacklisted(container) { log.Printf("Container %s is blacklisted, skipping", container.ContainerName) continue } ```