Skip to content

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

Client lifecycle

Container parsing flow

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.<alias>.<field> where <alias> is the route alias (or * for wildcard). The base labels apply to all routes.

LabelDescriptionExample
proxy.aliasesRoute aliases (comma-separated)proxy.aliases: www,app
proxy.excludeExclude from routingproxy.exclude: true
proxy.networkDocker networkproxy.network: frontend
proxy.<alias>.hostOverride hostnameproxy.app.host: 192.168.1.100
proxy.<alias>.portTarget portproxy.app.port: 8080
proxy.<alias>.schemeHTTP schemeproxy.app.scheme: https
proxy.<alias>.*Any route-specific settingproxy.app.no_tls_verify: true

Wildcard alias

Use proxy.*.<field> to apply settings to all routes:

yaml
labels:
  proxy.aliases: app1,app2
  proxy.*.scheme: https
  proxy.app1.port: 3000 # overrides wildcard

Idle watcher labels

LabelDescriptionExample
proxy.idle_timeoutIdle timeout durationproxy.idle_timeout: 30m
proxy.wake_timeoutMax time to wait for wakeproxy.wake_timeout: 10s
proxy.stop_methodStop method (pause, stop, kill)proxy.stop_method: stop
proxy.stop_signalSignal to send (e.g., SIGTERM)proxy.stop_signal: SIGTERM
proxy.stop_timeoutStop timeout in secondsproxy.stop_timeout: 30
proxy.depends_onContainer dependenciesproxy.depends_on: database
proxy.start_endpointOptional path restrictionproxy.start_endpoint: /api/ready
proxy.no_loading_pageSkip loading pageproxy.no_loading_page: true

Docker Compose labels

Those are created by Docker Compose.

LabelDescription
com.docker.compose.projectCompose project name
com.docker.compose.serviceService name
com.docker.compose.depends_onDependencies

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

FailureBehaviorRecovery
Docker socket inaccessibleNewClient returns errorFix socket permissions
Remote connection failedNewClient returns errorCheck network/tls config
Container inspect failedUpdatePorts returns errorContainer may be stopped
Invalid labelsContainer created with errorFix label syntax
Agent not foundPanic during client creationAdd 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
}

Released under the MIT License.