DockerRegistry¶
Not available until the next release of testcontainers-go main
Introduction¶
The Testcontainers module for Docker Registry.
Adding this module to your project dependencies¶
Please run the following command to add the Docker Registry module to your Go dependencies:
go get github.com/testcontainers/testcontainers-go/modules/dockerregistry
Usage example¶
package dockerregistry
import (
    "context"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/wait"
)
// DockerRegistryContainer represents the DockerRegistry container type used in the module
type DockerRegistryContainer struct {
    testcontainers.Container
}
// RunContainer creates an instance of the DockerRegistry container type
func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*DockerRegistryContainer, error) {
    req := testcontainers.ContainerRequest{
        Image:        "registry:2",
        ExposedPorts: []string{"5000:5000/tcp"},
        WaitingFor:   wait.ForExposedPort(),
    }
    genericContainerReq := testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started:          true,
    }
    for _, opt := range opts {
        opt.Customize(&genericContainerReq)
    }
    container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
    if err != nil {
        return nil, err
    }
    return &DockerRegistryContainer{Container: container}, nil
}
// WithAuthentication customizer that will retrieve a htpasswd file into the htpasswd directory given in input
func WithAuthentication(htpasswdPath string) testcontainers.CustomizeRequestOption {
    return func(req *testcontainers.GenericContainerRequest) {
        if req.Env == nil {
            req.Env = make(map[string]string)
        }
        req.Env["REGISTRY_AUTH"] = "htpasswd"
        req.Env["REGISTRY_AUTH_HTPASSWD_REALM"] = "Registry"
        req.Env["REGISTRY_AUTH_HTPASSWD_PATH"] = "/auth/htpasswd"
        if req.Mounts == nil {
            req.Mounts = testcontainers.ContainerMounts{}
        }
        htpasswdMount := testcontainers.ContainerMount{
            Source: testcontainers.GenericBindMountSource{
                HostPath: htpasswdPath,
            },
            Target: "/auth",
        }
        req.Mounts = append(req.Mounts, htpasswdMount)
    }
}
// WithData customizer that will retrieve a data directory and mount it inside the registry container
func WithData(dataDir string) testcontainers.CustomizeRequestOption {
    return func(req *testcontainers.GenericContainerRequest) {
        if req.Env == nil {
            req.Env = make(map[string]string)
        }
        req.Env["REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY"] = "/data"
        if req.Mounts == nil {
            req.Mounts = testcontainers.ContainerMounts{}
        }
        dataMount := testcontainers.ContainerMount{
            Source: testcontainers.GenericBindMountSource{
                HostPath: dataDir,
            },
            Target: "/data",
        }
        req.Mounts = append(req.Mounts, dataMount)
    }
}
// WithImage customizer that will override the registry image used
func WithImage(image string) testcontainers.CustomizeRequestOption {
    return func(req *testcontainers.GenericContainerRequest) {
        req.Image = image
    }
}
package dockerregistry
import (
    "context"
    "net/http"
    "os"
    "testing"
    "github.com/docker/go-connections/nat"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/wait"
)
var originalDockerAuthConfig string
func init() {
    originalDockerAuthConfig = os.Getenv("DOCKER_AUTH_CONFIG")
}
func TestDockerRegistry(t *testing.T) {
    ctx := context.Background()
    container, err := RunContainer(ctx)
    if err != nil {
        t.Fatal(err)
    }
    // Clean up the container after the test is complete
    t.Cleanup(func() {
        if err := container.Terminate(ctx); err != nil {
            t.Fatalf("failed to terminate container: %s", err)
        }
    })
    port, ipAddress := getRegistryPortAndAddress(t, err, container, ctx)
    // Let's simply check that the registry is up and running with a GET to http://localhost:5000/v2/_catalog
    resp, err := http.Get("http://" + ipAddress + ":" + port.Port() + "/v2/_catalog")
    if err != nil {
        // handle err
        t.Fatal(err)
    }
    defer resp.Body.Close()
    _, cancel := context.WithCancel(context.Background())
    t.Cleanup(cancel)
}
func TestDockerRegistryWithCustomImage(t *testing.T) {
    ctx := context.Background()
    container, err := RunContainer(ctx, WithImage("docker.io/registry:latest"))
    if err != nil {
        t.Fatal(err)
    }
    // Clean up the container after the test is complete
    t.Cleanup(func() {
        if err := container.Terminate(ctx); err != nil {
            t.Fatalf("failed to terminate container: %s", err)
        }
    })
    port, ipAddress := getRegistryPortAndAddress(t, err, container, ctx)
    // Let's check that the registry is up and running with a GET to http://localhost:5000/v2/_catalog also using a different image
    resp, err := http.Get("http://" + ipAddress + ":" + port.Port() + "/v2/_catalog")
    if err != nil {
        // handle err
        t.Fatal(err)
    }
    defer resp.Body.Close()
    _, cancel := context.WithCancel(context.Background())
    t.Cleanup(cancel)
}
func TestDockerRegistryWithData(t *testing.T) {
    ctx := context.Background()
    wd, err := os.Getwd()
    assert.NoError(t, err)
    container, err := RunContainer(ctx, WithData(wd+"/../../testdata/data"))
    if err != nil {
        t.Fatal(err)
    }
    // Clean up the container after the test is complete
    t.Cleanup(func() {
        if err := container.Terminate(ctx); err != nil {
            t.Fatalf("failed to terminate container: %s", err)
        }
    })
    // Let's check that we are able to start a container form image localhost:5000/redis:5.0-alpine
    req := testcontainers.ContainerRequest{
        Image:           "localhost:5000/redis:5.0-alpine",
        AlwaysPullImage: true, // make sure the authentication takes place
        ExposedPorts:    []string{"6379/tcp"},
        WaitingFor:      wait.ForLog("Ready to accept connections"),
    }
    redisContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started:          true,
    })
    require.Nil(t, err)
    terminateContainerOnEnd(t, ctx, redisContainer)
    _, cancel := context.WithCancel(context.Background())
    t.Cleanup(cancel)
}
func TestDockerRegistryWithAuth(t *testing.T) {
    ctx := context.Background()
    wd, err := os.Getwd()
    assert.NoError(t, err)
    container, err := RunContainer(ctx, WithAuthentication(wd+"/../../testdata/auth"))
    if err != nil {
        t.Fatal(err)
    }
    // Clean up the container after the test is complete
    t.Cleanup(func() {
        if err := container.Terminate(ctx); err != nil {
            t.Fatalf("failed to terminate container: %s", err)
        }
    })
    port, ipAddress := getRegistryPortAndAddress(t, err, container, ctx)
    // Let's simply check that the registry is up and running with a GET to http://localhost:5000/v2/_catalog
    h := http.Client{}
    req, _ := http.NewRequest("GET", "http://"+ipAddress+":"+port.Port()+"/v2/_catalog", nil)
    req.SetBasicAuth("testuser", "testpassword")
    resp, _ := h.Do(req)
    defer resp.Body.Close()
    _, cancel := context.WithCancel(context.Background())
    t.Cleanup(cancel)
}
func TestDockerRegistryWithAuthWithUnauthorizedRequest(t *testing.T) {
    ctx := context.Background()
    wd, err := os.Getwd()
    assert.NoError(t, err)
    container, err := RunContainer(ctx, WithAuthentication(wd+"/../../testdata/auth"))
    if err != nil {
        t.Fatal(err)
    }
    // Clean up the container after the test is complete
    t.Cleanup(func() {
        if err := container.Terminate(ctx); err != nil {
            t.Fatalf("failed to terminate container: %s", err)
        }
    })
    port, ipAddress := getRegistryPortAndAddress(t, err, container, ctx)
    // Let's simply check that the registry is up and running with a GET to http://localhost:5000/v2/_catalog
    h := http.Client{}
    req, _ := http.NewRequest("GET", "http://"+ipAddress+":"+port.Port()+"/v2/_catalog", nil)
    resp, err := h.Do(req)
    require.Equal(t, resp.StatusCode, 401)
    defer resp.Body.Close()
    _, cancel := context.WithCancel(context.Background())
    t.Cleanup(cancel)
}
func TestDockerRegistryWithAuthAndData(t *testing.T) {
    t.Cleanup(func() {
        os.Setenv("DOCKER_AUTH_CONFIG", originalDockerAuthConfig)
    })
    os.Unsetenv("DOCKER_AUTH_CONFIG")
    // using the same credentials as in the Docker Registry
    base64 := "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" // testuser:testpassword
    t.Setenv("DOCKER_AUTH_CONFIG", `{
        "auths": {
                "localhost:5000": { "username": "testuser", "password": "testpassword", "auth": "`+base64+`" }
        },
        "credsStore": "desktop"
    }`)
    ctx := context.Background()
    wd, err := os.Getwd()
    assert.NoError(t, err)
    container, err := RunContainer(ctx, WithAuthentication(wd+"/../../testdata/auth"), WithData(wd+"/../../testdata/data"))
    if err != nil {
        t.Fatal(err)
    }
    // Clean up the container after the test is complete
    t.Cleanup(func() {
        if err := container.Terminate(ctx); err != nil {
            t.Fatalf("failed to terminate container: %s", err)
        }
    })
    // Let's check that we are able to start a container form image localhost:5000/redis:5.0-alpine
    // using default username and password
    req := testcontainers.ContainerRequest{
        Image:           "localhost:5000/redis:5.0-alpine",
        AlwaysPullImage: true, // make sure the authentication takes place
        ExposedPorts:    []string{"6379/tcp"},
        WaitingFor:      wait.ForLog("Ready to accept connections"),
    }
    redisContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started:          true,
    })
    require.Nil(t, err)
    terminateContainerOnEnd(t, ctx, redisContainer)
    _, cancel := context.WithCancel(context.Background())
    t.Cleanup(cancel)
}
func TestDockerRegistryWithAuthDataAndImage(t *testing.T) {
    t.Cleanup(func() {
        os.Setenv("DOCKER_AUTH_CONFIG", originalDockerAuthConfig)
    })
    os.Unsetenv("DOCKER_AUTH_CONFIG")
    // using the same credentials as in the Docker Registry
    base64 := "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" // testuser:testpassword
    t.Setenv("DOCKER_AUTH_CONFIG", `{
        "auths": {
                "localhost:5000": { "username": "testuser", "password": "testpassword", "auth": "`+base64+`" }
        },
        "credsStore": "desktop"
    }`)
    ctx := context.Background()
    wd, err := os.Getwd()
    assert.NoError(t, err)
    container, err := RunContainer(ctx, WithAuthentication(wd+"/../../testdata/auth"), WithData(wd+"/../../testdata/data"), WithImage("docker.io/registry:latest"))
    if err != nil {
        t.Fatal(err)
    }
    // Clean up the container after the test is complete
    t.Cleanup(func() {
        if err := container.Terminate(ctx); err != nil {
            t.Fatalf("failed to terminate container: %s", err)
        }
    })
    // Let's check that we are able to start a container form image localhost:5000/redis:5.0-alpine
    // using default username and password
    req := testcontainers.ContainerRequest{
        Image:           "localhost:5000/redis:5.0-alpine",
        AlwaysPullImage: true, // make sure the authentication takes place
        ExposedPorts:    []string{"6379/tcp"},
        WaitingFor:      wait.ForLog("Ready to accept connections"),
    }
    redisContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started:          true,
    })
    require.Nil(t, err)
    terminateContainerOnEnd(t, ctx, redisContainer)
    _, cancel := context.WithCancel(context.Background())
    t.Cleanup(cancel)
}
func getRegistryPortAndAddress(t *testing.T, err error, container *DockerRegistryContainer, ctx context.Context) (nat.Port, string) {
    port, err := container.MappedPort(ctx, "5000")
    if err != nil {
        t.Fatal(err)
    }
    ipAddress, err := container.Host(ctx)
    if err != nil {
        t.Fatal(err)
    }
    return port, ipAddress
}
func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr testcontainers.Container) {
    tb.Helper()
    if ctr == nil {
        return
    }
    tb.Cleanup(func() {
        tb.Log("terminating container")
        require.NoError(tb, ctr.Terminate(ctx))
    })
}
Module reference¶
The Docker Registry module exposes one entrypoint function to create the Docker Registry container, and this function receives two parameters:
func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*DockerRegistryContainer, error)
- context.Context, the Go context.
- testcontainers.ContainerCustomizer, a variadic argument for passing options.
Container Options¶
When starting the Docker Registry container, you can pass options in a variadic way to configure it.
Image¶
If you need to set a different Docker Registry image, you can use testcontainers.WithImage with a valid Docker image
for DockerRegistry. E.g. testcontainers.WithImage("docker.io/registry:latest").
Wait Strategies¶
If you need to set a different wait strategy for Docker Registry, you can use testcontainers.WithWaitStrategy with a valid wait strategy
for DockerRegistry.
Info
The default deadline for the wait strategy is 60 seconds.
At the same time, it's possible to set a wait strategy and a custom deadline with testcontainers.WithWaitStrategyAndDeadline.
Docker type modifiers¶
If you need an advanced configuration for Docker Registry, you can leverage the following Docker type modifiers:
- testcontainers.WithConfigModifier
- testcontainers.WithHostConfigModifier
- testcontainers.WithEndpointSettingsModifier
Please read the Create containers: Advanced Settings documentation for more information.
Container Methods¶
The Docker Registry container exposes the following methods: