dockerpool

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 18, 2026 License: MIT Imports: 15 Imported by: 0

README

Dockerpool

A Go library for managing a pool of reusable Docker containers with automatic refill and graceful shutdown.

Features

  • Container pooling — pre-created containers ready for immediate use
  • Auto-refill — background worker maintains minimum idle containers
  • Graceful shutdown — waits for in-use containers before cleanup
  • Concurrent operations limit — prevents Docker daemon overload
  • Container sync on restart — recovers existing pool containers by labels
  • Race-free design — safe for concurrent use from multiple goroutines

Installation

go get github.com/bermanf/dockerpool

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/bermanf/dockerpool"
)

func main() {
    ctx := context.Background()

    // Create pool with minimal config
    pool, err := dockerpool.New(ctx, dockerpool.Config{
        Name:    "my-pool",
        Network: dockerpool.NetworkConfig{Name: "my-network", NeedsCreate: true},
        Image:   dockerpool.ImageConfig{Name: "alpine:latest", NeedsPull: true},
    })
    if err != nil {
        log.Fatal(err)
    }
    defer pool.Shutdown(ctx)

    // Acquire container from pool
    container, err := pool.Acquire(ctx)
    if err != nil {
        log.Fatal(err)
    }

    // Execute command
    result, err := container.Exec(ctx, []string{"echo", "Hello!"})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(result.Stdout)) // Hello!

    // Return container to pool for reuse
    pool.Return(ctx, container)
}

Configuration

Full Config Example
pool, err := dockerpool.New(ctx, dockerpool.Config{
    // Required
    Name: "my-pool",  // Pool name (used for container labels)
    
    Network: dockerpool.NetworkConfig{
        Name:        "my-network",    // Docker network name
        Driver:      "bridge",        // Network driver (default: bridge)
        NeedsCreate: true,            // Create network if not exists
        NeedsRemove: true,            // Remove network on shutdown
        Labels:      map[string]string{"env": "dev"},
    },
    
    Image: dockerpool.ImageConfig{
        Name:      "python:3.12-alpine", // Container image
        NeedsPull: true,                  // Pull image on pool creation
    },

    // Optional — have sensible defaults
    Cmd:                    []string{"sh"},             // Container command (default: ["sleep", "infinity"])
    MinIdle:                10,                         // Min idle containers (default: 5)
    MaxIdle:                100,                        // Max idle containers (default: 10)
    RefillInterval:         50 * time.Millisecond,      // Refill check interval (default: 100ms)
    MaxConcurrentDockerOps: 20,                         // Parallel Docker ops in refill/shutdown (default: 10)
    MaxConcurrentAcquire:   20,                         // Parallel container creation in Acquire (default: 10)

    // Advanced — custom container configuration
    ContainerConfig: &container.Config{
        Image: "python:3.12-alpine",
        Env:   []string{"PYTHONUNBUFFERED=1"},
    },
    HostConfig: &container.HostConfig{
        Resources: container.Resources{
            Memory:   128 * 1024 * 1024, // 128MB RAM limit
            NanoCPUs: 500_000_000,       // 0.5 CPU
        },
        NetworkMode: "none", // Disable networking inside container
    },

    // Custom labels for containers
    Labels: map[string]string{
        "app":         "code-runner",
        "environment": "production",
    },

    // Error callback (default: log.Printf)
    OnError: func(err error) {
        log.Printf("[POOL ERROR] %v", err)
    },
})
Config Reference
Field Type Default Description
Name string required Pool name, used for container labels
Network NetworkConfig required Docker network configuration
Image ImageConfig required Container image configuration
Cmd []string ["sleep", "infinity"] Command to run in containers
MinIdle int 5 Minimum idle containers to maintain
MaxIdle int 10 Maximum idle containers (excess removed)
RefillInterval time.Duration 100ms How often to check and refill pool
MaxConcurrentDockerOps int 10 Max parallel Docker API calls (refill, shutdown)
MaxConcurrentAcquire int 10 Max parallel container creation in Acquire when pool is empty
ContainerConfig *container.Config nil Custom container config (overrides Image, Cmd)
HostConfig *container.HostConfig nil Custom host config (resources, volumes)
Labels map[string]string nil Additional container labels
OnError func(error) log.Printf Error callback for async errors

Command Execution

Simple Execution
result, err := container.Exec(ctx, []string{"ls", "-la"})
if err != nil {
    // Docker API error
}
fmt.Printf("Exit: %d\nStdout: %s\nStderr: %s\n", 
    result.ExitCode, result.Stdout, result.Stderr)
With Timeout and Output Limit
result, err := container.ExecStd(ctx, []string{"python", "-c", userCode}, 
    dockerpool.ExecOptions{
        Timeout: 5 * time.Second,  // Kill after 5s
        Limit:   64 * 1024,        // 64KB max output
    },
)
if errors.Is(err, dockerpool.ErrOutputLimitExceeded) {
    // Output was truncated
}
Streaming Output

For large outputs or real-time logging, use ExecStream:

exitCode, err := container.ExecStream(ctx,
    []string{"sh", "-c", "echo building... && make && echo done"},
    os.Stdout,  // stdout writer
    os.Stderr,  // stderr writer  
    dockerpool.ExecOptions{Timeout: 5 * time.Minute},
)
With Stdin
input := strings.NewReader("line1\nline2\nline3\n")

var stdout bytes.Buffer
exitCode, err := container.ExecStream(ctx, 
    []string{"grep", "line2"},
    &stdout, 
    io.Discard,
    dockerpool.ExecOptions{Stdin: input},
)
fmt.Println(stdout.String()) // line2

Pool Operations

Acquire and Return
// Acquire container (from pool or creates new if empty)
container, err := pool.Acquire(ctx)
if errors.Is(err, dockerpool.ErrPoolShutdown) {
    // Pool is shutting down
}

// Return to pool (or removes if pool is full)
pool.Return(ctx, container)

// Remove without returning to pool (e.g., after error)
pool.Remove(ctx, container)
Metrics
idle := pool.IdleCount()  // Containers waiting in pool
inUse := pool.InUse()     // Containers currently acquired
Graceful Shutdown
// Shutdown waits for:
// 1. All in-use containers to be returned
// 2. All async removal operations to complete
// 3. Removes all idle containers
// 4. Optionally removes network

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := pool.Shutdown(ctx); err != nil {
    log.Printf("Shutdown error: %v", err)
}

Error Handling

container, err := pool.Acquire(ctx)
if err != nil {
    switch {
    case errors.Is(err, dockerpool.ErrPoolShutdown):
        // Pool is shut down, cannot acquire
    case errors.Is(err, context.DeadlineExceeded):
        // Context timeout
    default:
        // Docker API error
    }
}

Errors

Error Description
ErrPoolShutdown Returned when calling Acquire on a shut down pool
ErrPoolNameRequired Config validation: Name is empty
ErrNetworkNameRequired Config validation: Network.Name is empty
ErrImageRequired Config validation: Image.Name is empty
ErrOutputLimitExceeded Exec output exceeded the specified Limit

Performance Tips

  1. Tune MinIdle/MaxIdle — Set MinIdle high enough to handle burst traffic without synchronous container creation

  2. Adjust RefillInterval — Lower values mean faster recovery but more CPU usage; 50-100ms is a good range

  3. Limit concurrent opsMaxConcurrentDockerOps prevents overwhelming Docker daemon; increase for powerful servers

  4. Use ExecStream for large outputs — Avoids buffering entire output in memory

  5. Reuse containers — Always Return() healthy containers instead of Remove() to benefit from pooling

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        DockerPool                           │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   Config    │  │   Watcher   │  │   Pool (Stack)      │  │
│  │  (settings) │  │  (refill)   │  │   LIFO for cache    │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            │
                  Acquire() │ Return()
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                       Container                              │
│         Exec() / ExecStd() / ExecStream()                   │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                     Docker Daemon                            │
└─────────────────────────────────────────────────────────────┘

Thread Safety

All pool operations are safe for concurrent use:

  • Acquire(), Return(), Remove() — can be called from any goroutine
  • Shutdown() — blocks new Acquire() calls, waits for in-flight operations
  • IdleCount(), InUse() — atomic reads

License

MIT

Documentation

Overview

Example (Basic)

Example_basic demonstrates the simplest usage of dockerpool.

This example shows:

  • Creating a pool with minimal configuration
  • Acquiring a container from the pool
  • Executing a command and reading output
  • Returning the container to the pool
  • Graceful shutdown
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	// Create pool with minimal required configuration
	pool, err := dockerpool.New(ctx, dockerpool.Config{
		Name:    "basic-pool",
		Network: dockerpool.NetworkConfig{Name: "basic-net", NeedsCreate: true},
		Image:   dockerpool.ImageConfig{Name: "alpine:latest", NeedsPull: true},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer pool.Shutdown(ctx)

	// Acquire a container from the pool
	// If pool is empty, a new container is created synchronously
	c, err := pool.Acquire(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Execute a command inside the container
	result, err := c.Exec(ctx, []string{"echo", "Hello!"})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Print(string(result.Stdout))

	// Return the container to the pool for reuse
	// If pool is full (>= MaxIdle), container is removed instead
	pool.Return(ctx, c)
}
Example (CodeRunner)

Example_codeRunner demonstrates running user-provided code safely.

This example shows:

  • Pulling an image on pool creation
  • Creating a network if it doesn't exist
  • Using ExecStd with timeout and output limit
  • Typical code runner / sandbox pattern
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	pool, err := dockerpool.New(ctx, dockerpool.Config{
		Name: "python-runner",
		Network: dockerpool.NetworkConfig{
			Name:        "runner-net",
			NeedsCreate: true, // Create network if not exists
		},
		Image: dockerpool.ImageConfig{
			Name:      "python:3.12-alpine",
			NeedsPull: true, // Pull image on pool creation
		},
		MinIdle: 10, // Keep 10 containers warm
		MaxIdle: 50, // Allow up to 50 idle containers
	})
	if err != nil {
		log.Fatal(err)
	}
	defer pool.Shutdown(ctx)

	// User-provided code (untrusted input)
	userCode :=
		`
		print("Hello from Python!")
			for i in range(3):
   			print(f"  iteration {i}")
		`

	c, err := pool.Acquire(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer pool.Return(ctx, c)

	// Execute with safety limits
	result, err := c.ExecStd(ctx,
		[]string{"python", "-c", userCode},
		dockerpool.ExecOptions{
			Timeout: 5 * time.Second, // Kill if runs longer than 5s
			Limit:   64 * 1024,       // 64KB max output
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Exit: %d\n%s", result.ExitCode, result.Stdout)
}
Example (CustomLabels)

Example_customLabels demonstrates adding custom labels to containers.

This example shows:

  • Adding custom labels via Config.Labels
  • Reading labels from acquired containers
  • Using labels for monitoring/filtering
package main

import (
	"context"
	"fmt"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	pool, _ := dockerpool.New(ctx, dockerpool.Config{
		Name:    "labeled-pool",
		Network: dockerpool.NetworkConfig{Name: "labeled-net"},
		Image:   dockerpool.ImageConfig{Name: "alpine:latest"},

		// Custom labels added to all pool containers
		Labels: map[string]string{
			"app":         "code-runner",
			"environment": "production",
			"team":        "platform",
		},
	})
	defer pool.Shutdown(ctx)

	c, _ := pool.Acquire(ctx)
	defer pool.Return(ctx, c)

	// Labels are accessible on the container
	for k, v := range c.Labels() {
		fmt.Printf("%s=%s\n", k, v)
	}
}
Example (ErrorHandling)

Example_errorHandling demonstrates proper error handling patterns.

This example shows:

  • Handling Docker API errors vs command exit codes
  • When to use Return() vs Remove()
  • Using OnError callback for async errors
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	pool, _ := dockerpool.New(ctx, dockerpool.Config{
		Name:    "error-pool",
		Network: dockerpool.NetworkConfig{Name: "error-net"},
		Image:   dockerpool.ImageConfig{Name: "alpine:latest"},

		// Callback for async errors (refill failures, removal errors, etc.)
		OnError: func(err error) {
			log.Printf("[POOL ERROR] %v", err)
		},
	})
	defer pool.Shutdown(ctx)

	c, _ := pool.Acquire(ctx)

	// Execute a command that exits with error
	result, err := c.Exec(ctx, []string{"sh", "-c", "exit 1"})

	if err != nil {
		// Docker API error (container issue, network error, etc.)
		// Container might be in bad state — remove, don't reuse
		pool.Remove(ctx, c)
		return
	}

	if result.ExitCode != 0 {
		// Command failed, but container is healthy
		// Safe to return to pool
		fmt.Printf("Command failed: exit=%d, stderr=%s\n", result.ExitCode, result.Stderr)
		pool.Return(ctx, c)
		return
	}

	// Success
	pool.Return(ctx, c)
}
Example (GracefulShutdown)

Example_gracefulShutdown demonstrates proper shutdown with signal handling.

This example shows:

  • Handling SIGINT/SIGTERM for graceful shutdown
  • Using context timeout for shutdown deadline
  • Pattern for long-running services
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	pool, _ := dockerpool.New(ctx, dockerpool.Config{
		Name:    "shutdown-pool",
		Network: dockerpool.NetworkConfig{Name: "shutdown-net"},
		Image:   dockerpool.ImageConfig{Name: "alpine:latest"},
	})

	// Set up signal handler
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	// Run worker in background
	done := make(chan struct{})
	go func() {
		defer close(done)

		for i := 0; i < 100; i++ {
			c, err := pool.Acquire(ctx)
			if err != nil {
				return // Pool is shutting down
			}

			// Do work...
			c.Exec(ctx, []string{"sleep", "0.1"})

			pool.Return(ctx, c)
		}
	}()

	// Wait for signal or completion
	select {
	case <-sigCh:
		fmt.Println("Received signal, shutting down...")
	case <-done:
		fmt.Println("Work complete")
	}

	// Graceful shutdown with timeout
	// Waits for all in-use containers to be returned
	shutdownCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
	defer cancel()

	if err := pool.Shutdown(shutdownCtx); err != nil {
		log.Printf("Shutdown error: %v", err)
	}
}
Example (HighThroughput)

Example_highThroughput demonstrates configuration for high-load scenarios.

This example shows:

  • Tuning MinIdle/MaxIdle for burst traffic
  • Adjusting RefillInterval for faster recovery
  • Increasing MaxConcurrentDockerOps for parallel operations
  • Production-ready error handling
package main

import (
	"context"
	"log"
	"time"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	pool, err := dockerpool.New(ctx, dockerpool.Config{
		Name: "high-throughput",
		Network: dockerpool.NetworkConfig{
			Name:        "ht-net",
			NeedsCreate: true,
		},
		Image: dockerpool.ImageConfig{Name: "alpine:latest"},

		// Large pool for handling burst traffic
		MinIdle: 100, // Always keep 100 containers warm
		MaxIdle: 500, // Allow up to 500 idle containers

		// Aggressive refill for fast recovery
		RefillInterval:         50 * time.Millisecond,
		MaxConcurrentDockerOps: 50, // Parallel Docker operations

		// Production error handling
		OnError: func(err error) {
			// Send to monitoring system (Prometheus, Datadog, etc.)
			log.Printf("Pool error: %v", err)
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer pool.Shutdown(ctx)

	// Ready for high throughput...
}
Example (Metrics)

Example_metrics demonstrates monitoring pool state.

This example shows:

  • Reading IdleCount() and InUse() metrics
  • Pattern for exposing metrics to monitoring systems
  • Observing pool behavior under load
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	pool, _ := dockerpool.New(ctx, dockerpool.Config{
		Name:    "metrics-pool",
		Network: dockerpool.NetworkConfig{Name: "metrics-net"},
		Image:   dockerpool.ImageConfig{Name: "alpine:latest"},
		MinIdle: 5,
		MaxIdle: 20,
	})
	defer pool.Shutdown(ctx)

	// Metrics reporter (could export to Prometheus, etc.)
	go func() {
		for {
			time.Sleep(time.Second)
			fmt.Printf("pool_idle=%d pool_in_use=%d\n",
				pool.IdleCount(),
				pool.InUse(),
			)
		}
	}()

	// Simulate burst of requests
	for i := 0; i < 10; i++ {
		c, _ := pool.Acquire(ctx)
		go func() {
			time.Sleep(500 * time.Millisecond)
			pool.Return(ctx, c)
		}()
	}

	time.Sleep(2 * time.Second)
}
Example (NetworkIsolation)

Example_networkIsolation demonstrates creating isolated network per pool.

This example shows:

  • Creating a dedicated network for pool containers
  • Automatic network cleanup on shutdown
  • Containers can communicate within the network
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	pool, err := dockerpool.New(ctx, dockerpool.Config{
		Name: "isolated-pool",
		Network: dockerpool.NetworkConfig{
			Name:        "isolated-net-" + fmt.Sprint(time.Now().Unix()),
			Driver:      "bridge",
			NeedsCreate: true, // Create network on pool start
			NeedsRemove: true, // Remove network on shutdown
			Labels: map[string]string{
				"purpose": "isolation",
			},
		},
		Image: dockerpool.ImageConfig{Name: "alpine:latest"},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer pool.Shutdown(ctx) // Also removes the network

	c, _ := pool.Acquire(ctx)
	defer pool.Return(ctx, c)

	// Containers in this pool can communicate via the isolated network
	// but are isolated from other Docker networks
}
Example (ResourceLimits)

Example_resourceLimits demonstrates container resource constraints.

This example shows:

  • Setting memory and CPU limits via HostConfig
  • Disabling network access for sandboxing
  • Using container.Resources for fine-grained control
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/bermanf/dockerpool"
	"github.com/moby/moby/api/types/container"
)

func main() {
	ctx := context.Background()

	pool, err := dockerpool.New(ctx, dockerpool.Config{
		Name:    "limited-pool",
		Network: dockerpool.NetworkConfig{Name: "limited-net"},
		Image:   dockerpool.ImageConfig{Name: "alpine:latest"},

		// Custom host configuration with resource limits
		HostConfig: &container.HostConfig{
			Resources: container.Resources{
				Memory:   128 * 1024 * 1024, // 128MB RAM limit
				NanoCPUs: 500_000_000,       // 0.5 CPU cores
			},
			// Disable networking inside container (sandbox mode)
			NetworkMode: "none",
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer pool.Shutdown(ctx)

	c, _ := pool.Acquire(ctx)
	defer pool.Return(ctx, c)

	// Network access will fail due to NetworkMode: "none"
	result, _ := c.Exec(ctx, []string{"ping", "-c1", "8.8.8.8"})
	fmt.Printf("Network disabled: exit=%d\n", result.ExitCode)
}
Example (StdinPipe)

Example_stdinPipe demonstrates passing data to a command via stdin.

This example shows:

  • Using ExecStream for streaming I/O
  • Passing input via ExecOptions.Stdin
  • Capturing output to a buffer
package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	pool, _ := dockerpool.New(ctx, dockerpool.Config{
		Name:    "pipe-pool",
		Network: dockerpool.NetworkConfig{Name: "pipe-net"},
		Image:   dockerpool.ImageConfig{Name: "alpine:latest"},
	})
	defer pool.Shutdown(ctx)

	c, _ := pool.Acquire(ctx)
	defer pool.Return(ctx, c)

	// Data to process via stdin
	input := strings.NewReader("apple\nbanana\ncherry\napricot\n")

	var stdout bytes.Buffer
	exitCode, err := c.ExecStream(ctx,
		[]string{"grep", "^a"}, // Match lines starting with 'a'
		&stdout,
		nil, // Discard stderr
		dockerpool.ExecOptions{Stdin: input},
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("exit=%d\n%s", exitCode, stdout.String())
}
Example (StreamToFile)

Example_streamToFile demonstrates streaming command output directly to a file.

This example shows:

  • Using ExecStream with file writers
  • Avoiding memory pressure for large outputs
  • Real-time logging pattern
package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/bermanf/dockerpool"
)

func main() {
	ctx := context.Background()

	pool, _ := dockerpool.New(ctx, dockerpool.Config{
		Name:    "stream-pool",
		Network: dockerpool.NetworkConfig{Name: "stream-net"},
		Image:   dockerpool.ImageConfig{Name: "alpine:latest"},
	})
	defer pool.Shutdown(ctx)

	c, _ := pool.Acquire(ctx)
	defer pool.Return(ctx, c)

	// Create log file for output
	logFile, _ := os.CreateTemp("", "build-*.log")
	defer os.Remove(logFile.Name())
	defer logFile.Close()

	// Stream output directly to file (no memory buffering)
	exitCode, _ := c.ExecStream(ctx,
		[]string{"sh", "-c", "echo 'Building...'; sleep 1; echo 'Done!'"},
		logFile,   // stdout → file
		os.Stderr, // stderr → console
		dockerpool.ExecOptions{Timeout: 30 * time.Second},
	)

	fmt.Printf("Build finished: exit=%d, log=%s\n", exitCode, logFile.Name())
}

Index

Examples

Constants

View Source
const (
	LabelManagedBy      = "dockerpool.managed-by"
	LabelManagedByValue = "dockerpool"
	LabelPoolName       = "dockerpool.pool-name"
)

Labels for identifying pool containers

Variables

View Source
var (
	// ErrImageRequired is returned when Docker image is not specified
	ErrImageRequired = errors.New("image is required")

	// ErrOutputLimitExceeded is returned when exec output exceeds the limit
	ErrOutputLimitExceeded = errors.New("output limit exceeded")

	// ErrPoolNameRequired is returned when pool name is empty
	ErrPoolNameRequired = errors.New("pool name is required")

	// ErrNetworkNameRequired is returned when network name is empty
	ErrNetworkNameRequired = errors.New("network name is required")

	// ErrPoolShutdown is returned when operations are attempted on a shut down pool
	ErrPoolShutdown = errors.New("pool is shut down")
)

Functions

func MergeLabels

func MergeLabels(labelMaps ...map[string]string) map[string]string

MergeLabels merges multiple map[string]string into one. Later maps overwrite values from earlier ones.

func PoolLabels

func PoolLabels(poolName string) map[string]string

PoolLabels creates base labels for a pool container

Types

type Config added in v1.0.1

type Config struct {
	// Required
	Name    string        // pool name (used for container labels)
	Network NetworkConfig // docker network configuration
	Image   ImageConfig   // container image configuration

	// Optional - have sensible defaults
	Cmd                    []string      // container command (default: ["sleep", "infinity"])
	MinIdle                int           // minimum idle containers (default: 5)
	MaxIdle                int           // maximum idle containers (default: 10)
	RefillInterval         time.Duration // pool refill check interval (default: 100ms)
	MaxConcurrentDockerOps int           // max parallel docker operations (default: 10)
	MaxConcurrentAcquire   int           // max parallel container acquisition (default: 10)

	// Advanced - for custom container configuration
	ContainerConfig *container.Config     // custom container config (overrides Image, Cmd)
	HostConfig      *container.HostConfig // custom host config (volumes, resources, etc.)
	Labels          map[string]string     // additional container labels

	// Callbacks
	OnError func(error) // error handler (default: log.Printf)
}

Config contains all configuration for creating a DockerPool.

type Container

type Container interface {
	ID() string
	Image() string
	Labels() map[string]string
	State() ContainerState
	Stop(ctx context.Context) error
	Start(ctx context.Context) error
	Exec(ctx context.Context, cmd []string) (*ExecResult, error)
	ExecStd(ctx context.Context, cmd []string, opts ExecOptions) (*ExecResult, error)
	ExecStream(ctx context.Context, cmd []string, stdout, stderr io.Writer, opts ExecOptions) (exitCode int, err error)
}

Container interface for working with a container

func NewContainerClient

func NewContainerClient(opts ContainerOpts) Container

type ContainerClient

type ContainerClient struct {
	// contains filtered or unexported fields
}

ContainerClient is the implementation of the Container interface

func (*ContainerClient) Exec

func (c *ContainerClient) Exec(ctx context.Context, cmd []string) (*ExecResult, error)

func (*ContainerClient) ExecStd

func (c *ContainerClient) ExecStd(ctx context.Context, cmd []string, opts ExecOptions) (*ExecResult, error)

func (*ContainerClient) ExecStream

func (c *ContainerClient) ExecStream(ctx context.Context, cmd []string, stdout, stderr io.Writer, opts ExecOptions) (int, error)

func (*ContainerClient) ID

func (c *ContainerClient) ID() string

func (*ContainerClient) Image

func (c *ContainerClient) Image() string

func (*ContainerClient) Labels

func (c *ContainerClient) Labels() map[string]string

func (*ContainerClient) Start added in v1.0.1

func (c *ContainerClient) Start(ctx context.Context) error

func (*ContainerClient) State

func (c *ContainerClient) State() ContainerState

func (*ContainerClient) Stop

func (c *ContainerClient) Stop(ctx context.Context) error

type ContainerOpts

type ContainerOpts struct {
	Docker Docker
	ID     string
	Image  string
	Labels map[string]string
	State  ContainerState
}

type ContainerState

type ContainerState string
const (
	StateCreated    ContainerState = "created"
	StateRunning    ContainerState = "running"
	StatePaused     ContainerState = "paused"
	StateRestarting ContainerState = "restarting"
	StateRemoving   ContainerState = "removing"
	StateExited     ContainerState = "exited"
	StateDead       ContainerState = "dead"
)

Container states

type CreateContainerOptions

type CreateContainerOptions struct {
	Config     *container.Config     // Container configuration (image, cmd, env, labels, etc.)
	HostConfig *container.HostConfig // Host settings (volumes, network, resources, etc.)
}

CreateContainerOptions contains options for creating a container

type Docker

type Docker interface {
	// Close closes the connection to Docker
	Close() error

	// Ping checks connection to Docker daemon
	Ping(ctx context.Context) error

	// Image operations
	PullImage(ctx context.Context, image string) error

	// Network operations
	EnsureNetwork(ctx context.Context, networkName string, driver string, labels map[string]string) (bool, error)
	RemoveNetwork(ctx context.Context, networkName string) error

	// Container operations
	StartContainer(ctx context.Context, containerID string, opts client.ContainerStartOptions) error
	CreateContainer(ctx context.Context, networkName string, opts CreateContainerOptions) (Container, error)
	StopContainer(ctx context.Context, containerID string, opts client.ContainerStopOptions) error
	RemoveContainer(ctx context.Context, containerID string) error

	// Exec operations
	Exec(ctx context.Context, containerID string, cmd []string) (*ExecResult, error)
	ExecStd(ctx context.Context, containerID string, cmd []string, opts ExecOptions) (*ExecResult, error)
	ExecStream(ctx context.Context, containerID string, cmd []string, stdout, stderr io.Writer, opts ExecOptions) (exitCode int, err error)

	// List operations
	ListContainersByLabels(ctx context.Context, labels ...Label) ([]Container, error)
}

Docker is the interface for working with Docker.

type DockerPool

type DockerPool struct {
	// contains filtered or unexported fields
}

DockerPool manages a pool of Docker containers.

func New added in v1.0.1

func New(ctx context.Context, cfg Config) (*DockerPool, error)

New creates a new DockerPool. This is the main entry point for users.

func NewWithDocker added in v1.0.1

func NewWithDocker(ctx context.Context, docker Docker, cfg Config) (*DockerPool, error)

NewWithDocker creates a pool with a provided Docker implementation. Use this for testing with mocks or custom Docker clients.

func (*DockerPool) Acquire

func (p *DockerPool) Acquire(ctx context.Context) (Container, error)

Acquire gets a container from the pool or creates a new one. After use, you must call Return() or Remove(). Returns ErrPoolShutdown if the pool has been shut down.

func (*DockerPool) IdleCount

func (p *DockerPool) IdleCount() int

IdleCount returns the number of idle containers in the pool.

func (*DockerPool) InUse

func (p *DockerPool) InUse() int64

InUse returns the number of containers currently in use.

func (*DockerPool) Remove

func (p *DockerPool) Remove(ctx context.Context, c Container)

Remove removes the container (does not return it to the pool).

func (*DockerPool) Return

func (p *DockerPool) Return(ctx context.Context, c Container)

Return returns the container to the pool. If the pool is full (>= maxIdle) or shutdown is in progress, the container is removed.

func (*DockerPool) Shutdown

func (p *DockerPool) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the pool. It waits for all in-use containers to be returned, then removes all containers.

type ExecOptions

type ExecOptions struct {
	Stdin   io.Reader     // Input to pass to the command
	Timeout time.Duration // If > 0, command will be cancelled after timeout
	Limit   int64         // If > 0, the output will be limited to the given number of bytes
}

ExecOptions contains options for executing a command in a container

type ExecResult

type ExecResult struct {
	ExitCode int
	Stdout   []byte
	Stderr   []byte
}

ExecResult contains the result of executing a command in a container

type ImageConfig added in v1.0.1

type ImageConfig struct {
	Name      string
	NeedsPull bool
}

type Label

type Label struct {
	Key   string
	Value string
}

Label represents a key-value pair for filtering containers

type NetworkConfig added in v1.0.1

type NetworkConfig struct {
	Name        string
	Driver      string
	Labels      map[string]string
	NeedsCreate bool
	NeedsRemove bool
}

type Stack added in v1.0.1

type Stack[T any] interface {
	Push(elem T)
	Pop() (elem T, ok bool)
	Len() int
}

Stack is a thread-safe LIFO stack.

func NewStack added in v1.0.1

func NewStack[T any]() Stack[T]

NewStack creates a new thread-safe stack.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL