docker

package module
v1.3.1 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

go-docker-testsuite

Go Reference Go Version Last Commit License PRs Welcome

Go library to run third-party dependencies in Docker containers for integration testing. Spin up any Docker image — databases, queues, caches, or object storage — and connect to them via network from your Go tests.


Features

  • Container — low-level wrapper to create, run, await output, and clean up any Docker image
  • Group — run multiple containers in an isolated Docker network with IP-level connectivity
  • Applications — ready-to-use wrappers for popular services (MySQL, PostgreSQL, Redis, Kafka, etc.)
  • Hooks — lifecycle callbacks (BeforeRun, AfterRun, BeforeClose, AfterClose) per container
  • Matchers — await container logs with substring, exact, or regexp matchers before proceeding
  • Environment builder — fluent DSL to declare typed environment variables
  • Port bindings — DNAT port mapping with random or one-to-one port allocation
  • IMAGE_PREFIX — optional IMAGE_PREFIX env var to route images through a proxy/mirror

Requirements

  • Go 1.26+ (uses go.1.26.0 directive in go.mod)
  • A running Docker daemon (also works with remote Docker hosts via DOCKER_HOST, etc.)

Installation

go get github.com/teran/go-docker-testsuite

Applications

The test suite provides ready-to-use wrappers (each returns a typed client interface and handles startup, health checks, and cleanup). Here's the full list:

Application Package Description
Kafka applications/kafka Apache Kafka with Sarama client
Memcache applications/memcache Memcached with gomemcache client
MinIO applications/minio S3-compatible object storage
MySQL / MariaDB / Percona Server applications/mysql MySQL-compatible databases
PostgreSQL applications/postgres PostgreSQL with pgx client
Redis applications/redis Redis with go-redis client
ScyllaDB applications/scylladb ScyllaDB with gocql client
Vault applications/vault HashiCorp Vault
applications/*/versions/ Per-version integration tests

Many application packages include testable Examples (Example* functions in *_test.go files) that demonstrate real usage. They are displayed on pkg.go.dev and can be verified locally:

# Run all examples (requires a running Docker daemon):
go test -run Example ./applications/... .

# Run a specific example:
go test -run "^Example$" ./applications/mysql/

Usage

Quick start — MySQL
package main

import (
    "context"
    "database/sql"
    "time"

    _ "github.com/go-sql-driver/mysql"

    "github.com/teran/go-docker-testsuite/applications/mysql"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
    defer cancel()

    app, err := mysql.New(ctx, "index.docker.io/library/mysql:8.0.4")
    if err != nil {
        panic(err)
    }
    defer app.Close(ctx)

    if err := app.CreateDB(ctx, "important_database"); err != nil {
        panic(err)
    }

    db, err := sql.Open("mysql", app.MustDSN("important_database"))
    if err != nil {
        panic(err)
    }
    defer db.Close()

    if _, err := db.ExecContext(ctx, "SELECT 1"); err != nil {
        panic(err)
    }
}
Multi-container group

Use docker.Group to run several containers in an isolated Docker network with internal DNS resolution:

package main

import (
    "context"
    "time"

    "github.com/teran/go-docker-testsuite"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
    defer cancel()

    app := docker.NewApplication(
        c,
        docker.HookFunc(func(ctx context.Context, ht docker.HookType, c docker.Container) error {
            // e.g. wait for readiness before moving on
            return c.AwaitOutput(ctx, docker.NewSubstringMatcher("ready"))
        }),
    )

    g, err := docker.NewGroup("my-services", app1, app2)
    if err != nil {
        panic(err)
    }

    if err := g.Run(ctx); err != nil {
        panic(err)
    }
    defer g.Close(ctx)
}
Lifecycle hooks

Every container supports hooks at four stages:

docker.HookTypeBeforeRun   // before container starts
docker.HookTypeAfterRun    // after container starts
docker.HookTypeBeforeClose // before container stops
docker.HookTypeAfterClose  // after container stops

Pass hooks via docker.NewApplication(container, hook1, hook2, ...).

Image prefix / proxy

Set the IMAGE_PREFIX environment variable to prepend a registry mirror to all image references:

# Use a local mirror instead of Docker Hub
export IMAGE_PREFIX=registry-mirror.example.com

Examples

Each application package includes testable examples. Run them with:

# Run all examples (needs Docker):
go test -run Example ./applications/... .

Project docs

License

This project is licensed under the Apache License, Version 2.0.

Documentation

Overview

Example (Container)

This example demonstrates using the low-level Container API: creating a container from a custom image, configuring environment variables and port bindings, waiting for a log line, and making gRPC calls.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	"github.com/teran/echo-grpc-server/presenter/proto"

	docker "github.com/teran/go-docker-testsuite"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
	defer cancel()

	c, err := docker.NewContainer(
		"echo-server",
		"ghcr.io/teran/echo-grpc-server:latest",
		nil,
		docker.NewEnvironment().
			StringVar("ADDR", ":5555").
			LogLevelVar("LOG_LEVEL", logrus.TraceLevel),
		docker.NewPortBindings().
			PortDNAT(docker.ProtoTCP, 5555),
	)
	if err != nil {
		fmt.Printf("error creating container: %v\n", err)
		return
	}
	defer func() { _ = c.Close(ctx) }()

	if err := c.Run(ctx); err != nil {
		fmt.Printf("error running container: %v\n", err)
		return
	}

	if err := c.AwaitOutput(ctx, docker.NewSubstringMatcher("running GRPC echo server")); err != nil {
		fmt.Printf("error waiting for server: %v\n", err)
		return
	}
	fmt.Println("server is ready")

	hp, err := c.URL(docker.ProtoTCP, 5555)
	if err != nil {
		fmt.Printf("error getting URL: %v\n", err)
		return
	}

	conn, err := grpc.NewClient(hp.String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Printf("error dialing: %v\n", err)
		return
	}
	defer func() { _ = conn.Close() }()

	cli := proto.NewEchoServiceClient(conn)
	resp, err := cli.Echo(ctx, &proto.EchoRequest{Message: "Hello!"})
	if err != nil {
		fmt.Printf("error calling Echo: %v\n", err)
		return
	}
	fmt.Printf("echo response: %s\n", resp.GetMessage())
}
Example (Group)

This example demonstrates the Group API: running two containers on the same internal Docker network so they can reach each other by container name.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	"github.com/teran/echo-grpc-server/presenter/proto"

	docker "github.com/teran/go-docker-testsuite"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
	defer cancel()

	awaitRunFn := func(ctx context.Context, ht docker.HookType, c docker.Container) error {
		if ht == docker.HookTypeAfterRun {
			return c.AwaitOutput(ctx, docker.NewSubstringMatcher("running GRPC echo server"))
		}
		return nil
	}

	svr, err := docker.NewContainer(
		"my-server",
		"ghcr.io/teran/echo-grpc-server:latest",
		nil,
		docker.NewEnvironment().
			StringVar("ADDR", ":5555").
			LogLevelVar("LOG_LEVEL", logrus.TraceLevel),
		docker.NewPortBindings().
			PortDNAT(docker.ProtoTCP, 5555),
	)
	if err != nil {
		fmt.Printf("error creating server container: %v\n", err)
		return
	}

	client, err := docker.NewContainer(
		"my-client",
		"ghcr.io/teran/echo-grpc-server:latest",
		nil,
		docker.NewEnvironment().
			StringVar("ADDR", ":5555").
			LogLevelVar("LOG_LEVEL", logrus.TraceLevel),
		docker.NewPortBindings().
			PortDNAT(docker.ProtoTCP, 5555),
	)
	if err != nil {
		fmt.Printf("error creating client container: %v\n", err)
		return
	}

	g, err := docker.NewGroup("my-group",
		docker.NewApplication(svr, awaitRunFn),
		docker.NewApplication(client, awaitRunFn),
	)
	if err != nil {
		fmt.Printf("error creating group: %v\n", err)
		return
	}
	defer func() { _ = g.Close(ctx) }()

	if err := g.Run(ctx); err != nil {
		fmt.Printf("error running group: %v\n", err)
		return
	}
	fmt.Println("group started")

	// Connect to client and call server by its DNS name.
	hp, err := client.URL(docker.ProtoTCP, 5555)
	if err != nil {
		fmt.Printf("error getting client URL: %v\n", err)
		return
	}

	conn, err := grpc.NewClient(hp.String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Printf("error dialing: %v\n", err)
		return
	}
	defer func() { _ = conn.Close() }()

	cli := proto.NewRemoteEchoServiceClient(conn)
	resp, err := cli.RemoteEcho(ctx, &proto.RemoteEchoRequest{
		Remote:  "my-server:5555",
		Message: "Hello across containers!",
	})
	if err != nil {
		fmt.Printf("error calling RemoteEcho: %v\n", err)
		return
	}
	fmt.Printf("remote echo response: %s\n", resp.GetMessage())
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrPortNotMapped             = errors.New("port not mapped")
	ErrDockerHostIPIsNotResolved = errors.New("docker host IP address cannot be resolved")
)

Functions

func DockerIP

func DockerIP() (string, error)

DockerIP returns docker node IP address for further connectivity usage

func NewHostConfig

func NewHostConfig(pb *PortBindings, opts ...ContainerOption) (*dockerContainer.HostConfig, error)

NewHostConfig creates new HostConfig instance

func OneToOneRandomPort added in v1.1.0

func OneToOneRandomPort(proto Protocol, srcPort uint16) (string, uint16, []string, error)

func RandomPort added in v1.1.0

func RandomPort(proto Protocol, dstPort uint16) (string, uint16, []string, error)

RandomPortTCP makes a query to the kernel about free high range IP address NOTE: it have some probability impact so could fail in the really small amount of cases

Types

type Application

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

func NewApplication

func NewApplication(c Container, hooks ...Hook) *Application

type Binding

type Binding struct {
	HostIP   string
	HostPort string
}

Binding reflects a mapping part of the internal & external port of the container

type Container

type Container interface {
	AwaitOutput(ctx context.Context, m Matcher) error
	GetOutput(ctx context.Context, m ...Matcher) ([]string, error)
	Close(ctx context.Context) error
	ID() ContainerID
	Name() string
	NetworkAttach(networkID string) error
	Ping(ctx context.Context) error
	Run(ctx context.Context) error
	URL(proto Protocol, port uint16) (*HostPort, error)
}

Container exposes interface to control the container runtime

func NewContainer

func NewContainer(name, image string, cmd []string, environment Environment, ports *PortBindings, opts ...ContainerOption) (Container, error)

New creates new container instance from remote docker image

func NewContainerWithClient

func NewContainerWithClient(cli *client.Client, name, image string, cmd []string, env Environment, ports *PortBindings, opts ...ContainerOption) (Container, error)

NewContainerWithClient creates new container from remote docker image and allows to pass custom docker.Client instance

type ContainerID

type ContainerID = string

type ContainerInfo added in v1.1.0

type ContainerInfo interface {
	GetExternalPortMapping(Protocol, uint16) (uint16, error)
	GetDockerHostIP() (string, error)
}

type ContainerOption added in v1.3.0

type ContainerOption func(*dockerContainer.HostConfig)

ContainerOption modifies the docker HostConfig before container creation.

func WithBinds added in v1.3.0

func WithBinds(binds ...string) ContainerOption

WithBinds adds volume bind mounts (host:container[:mode]).

func WithPrivileged added in v1.3.0

func WithPrivileged() ContainerOption

WithPrivileged grants the container elevated privileges.

func WithTmpfs added in v1.3.0

func WithTmpfs(m map[string]string) ContainerOption

WithTmpfs mounts tmpfs filesystems at the given paths.

type Environment

type Environment map[string]func(c ContainerInfo) string

Environment represents the container environment passed into runtime

func NewEnvironment

func NewEnvironment() Environment

NewEnvironment creates new Environment instance

func (Environment) BoolVar

func (e Environment) BoolVar(name string, value bool) Environment

BoolVar sets bool var to the environment

func (Environment) Eval added in v1.1.0

func (e Environment) Eval(c ContainerInfo) (es []string)

func (Environment) Int8Var

func (e Environment) Int8Var(name string, value int8) Environment

Int8Var sets int8 var to the environment

func (Environment) Int16Var

func (e Environment) Int16Var(name string, value int16) Environment

Int16Var sets int16 var to the environment

func (Environment) Int32Var

func (e Environment) Int32Var(name string, value int32) Environment

Int32Var sets int32 var to the environment

func (Environment) Int64Var

func (e Environment) Int64Var(name string, value int64) Environment

Int64Var sets int64 var to the environment

func (Environment) IntVar

func (e Environment) IntVar(name string, value int) Environment

IntVar sets int var to the environment

func (Environment) LogLevelVar

func (e Environment) LogLevelVar(name string, l log.Level) Environment

LogLevelVar sets logrus.Level var to the environment

func (Environment) StringVar

func (e Environment) StringVar(name, value string) Environment

StringVar sets string var to the environment

func (Environment) Uint8Var

func (e Environment) Uint8Var(name string, value uint8) Environment

Uint8Var sets uint8 var to the environment

func (Environment) Uint16Var

func (e Environment) Uint16Var(name string, value uint16) Environment

Uint16Var sets uint16 var to the environment

func (Environment) Uint32Var

func (e Environment) Uint32Var(name string, value uint32) Environment

Uint32Var sets uint32 var to the environment

func (Environment) Uint64Var

func (e Environment) Uint64Var(name string, value uint64) Environment

Uint64Var sets uint64 var to the environment

func (Environment) UintVar

func (e Environment) UintVar(name string, value uint) Environment

UintVar sets uint var to the environment

func (Environment) Var added in v1.1.0

func (e Environment) Var(name string, vfn func(c ContainerInfo) string) Environment

Var allows to set custom function to generate environment variable

type Group

type Group interface {
	Run(ctx context.Context) error
	Close(ctx context.Context) error
}

func NewGroup

func NewGroup(name string, apps ...*Application) (Group, error)

func NewGroupWithClient

func NewGroupWithClient(cli *client.Client, name string, apps ...*Application) (Group, error)

type Hook

type Hook func(context.Context, HookType, Container) error

type HookType

type HookType string
const (
	HookTypeBeforeRun   HookType = "before_run"
	HookTypeAfterRun    HookType = "after_run"
	HookTypeBeforeClose HookType = "before_close"
	HookTypeAfterClose  HookType = "after_close"
)

type HostConfigSpec

type HostConfigSpec struct {
	PortBindings map[string][]Binding
}

HostConfigSpec is just a wrapper structure to pass host configuration to the container

type HostPort

type HostPort struct {
	Host string
	Port uint16
}

HostPort allows to return host & port from the URL method

func (*HostPort) String

func (hp *HostPort) String() string

String returns IP:Port pair

type Matcher

type Matcher func(l string) bool

Matcher allows to create any kind of matcher for container outputs

func NewExactMatcher

func NewExactMatcher(s string) Matcher

NewExactMatcher represents exact matcher i.e. the output should be exactly matched (except space chars around the word)

func NewRegexpMatcher

func NewRegexpMatcher(r *regexp.Regexp) Matcher

NewRegexpMatcher returns a matcher that succeeds when the line matches the compiled regular expression.

func NewSubstringMatcher

func NewSubstringMatcher(s string) Matcher

NewSubstringMatcher represents partial matcher

type NetworkID

type NetworkID = string

type PortAllocator added in v1.1.0

type PortAllocator func(proto Protocol, port uint16) (string, uint16, []string, error)

type PortBindings

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

PortBindings is a full mapping of internal & external docker container ports

func NewDirectPortBinding added in v1.1.0

func NewDirectPortBinding() *PortBindings

func NewPortBindings

func NewPortBindings() *PortBindings

NewPortBindings creates new PortBindings instance

func NewPortBindingsWithPortAllocator added in v1.1.0

func NewPortBindingsWithPortAllocator(allocator PortAllocator) *PortBindings

NewPortBindingsWithTCPPortAllocator creates new PortBinding instance and allows to pass custom port allocation function

func (*PortBindings) PortDNAT

func (pb *PortBindings) PortDNAT(proto Protocol, port uint16) *PortBindings

PortDNAT adds new port to be exposed from the container

type Protocol

type Protocol string

Protocol to be NAT'ed from the container

const (
	// ProtoTCP ...
	ProtoTCP Protocol = "tcp"

	// ProtoUDP ...
	ProtoUDP Protocol = "udp"
)

func (Protocol) String

func (p Protocol) String() string

String returns the string representation of the protocol

Directories

Path Synopsis
applications
k3s
Package k3s provides a K3s container for integration testing.
Package k3s provides a K3s container for integration testing.
k3s/versions
Package versions provides a shared test suite for versioned K3s images.
Package versions provides a shared test suite for versioned K3s images.
internal
ptr
tools
cmd/split_test_groups command
split_test_groups discovers Go test packages and groups them into balanced CI matrix groups by application type.
split_test_groups discovers Go test packages and groups them into balanced CI matrix groups by application type.

Jump to

Keyboard shortcuts

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