testcontainers-java icon indicating copy to clipboard operation
testcontainers-java copied to clipboard

[Bug]: cannot push to the docker registry started in testcontainers

Open pfilaretov42 opened this issue 1 year ago • 4 comments

Module

Core

Testcontainers version

1.19.8

Using the latest Testcontainers version?

No

Host OS

MacOS Sonoma 14.6.1

Host Arch

Tried both x86 and ARM

Docker version

Client: Cloud integration: v1.0.35+desktop.13 Version: 26.0.0 API version: 1.45 Go version: go1.21.8 Git commit: 2ae903e Built: Wed Mar 20 15:14:46 2024 OS/Arch: darwin/arm64 Context: desktop-linux

Server: Docker Desktop 4.29.0 (145265) Engine: Version: 26.0.0 API version: 1.45 (minimum version 1.24) Go version: go1.21.8 Git commit: 8b79278 Built: Wed Mar 20 15:18:02 2024 OS/Arch: linux/arm64 Experimental: false containerd: Version: 1.6.28 GitCommit: ae07eda36dd25f8a1b98dfbf587313b99c0190bb runc: Version: 1.1.12 GitCommit: v1.1.12-0-g51d5e94 docker-init: Version: 0.19.0 GitCommit: de40ad0

What happened?

I'm building a service with spring boot 3 that talks to k8s API using fabric8 java client. So, I want to run full integration tests without any mocks, including k8s cluster. To start k8s in tests I'm using k3s-testcontainers. It starts fine, but then I need a custom registry for k3s to pull my private images from during tests. So I thought I can start a docker registry in testcontainers for this.

The registry starts fine in testcontainers, but I cannot push anything to it. Neither in the code, nor through docker cli when the test is paused in the debug mode. I prepared a minimal demo to reproduce the issue: https://github.com/pfilaretov42/spring-testcontainers-registry

Here is how to reproduce the issue using my repository above:

  • Set breakpoint in TestcontainersTest class, on "set breakpoint here" line.
  • Debug TestcontainersTest.test() (e.g. in IntelliJ IDEA)
  • When paused, notice the mapped port for the registry container, e.g. 57025:
    docker ps | grep registry
    
  • Pull, tag, and push image to the registry:
% docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
478afc919002: Pull complete 
Digest: sha256:91fb4b041da273d5a3273b6d587d62d518300a6ad268b28628f74997b93171b2
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
What's Next?
  1. Sign in to your Docker account → docker login
  2. View a summary of image vulnerabilities and recommendations → docker scout quickview hello-world

% docker tag hello-world localhost:57025/hello-world
% docker push localhost:57025/hello-world
Using default tag: latest
The push refers to repository [localhost:57025/hello-world]
Get "http://localhost:57025/v2/": dial tcp [::1]:57025: connect: connection refused

% docker tag hello-world 127.0.0.1:57025/hello-world
% docker push 127.0.0.1:57025/hello-world
Using default tag: latest
The push refers to repository [127.0.0.1:57025/hello-world]
Get "http://127.0.0.1:57025/v2/": dial tcp 127.0.0.1:57025: connect: connection refused

% docker tag hello-world 0.0.0.0:57025/hello-world
% docker push 0.0.0.0:57025/hello-world
Using default tag: latest
The push refers to repository [0.0.0.0:57025/hello-world]
Get "https://0.0.0.0:57025/v2/": http: server gave HTTP response to HTTPS client

As you can see I tried different host names (localhost, 127.0.0.1, 0.0.0.0) but nothing works.

And here is how it works with plain docker cli, i.e. no testcontainers:

% docker run -d --rm -p 5000:5000 --name registry registry:2
443f9ea881257529793b059442a24be96e992123a484b756b8d1ae41eec4a332

% docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
Digest: sha256:91fb4b041da273d5a3273b6d587d62d518300a6ad268b28628f74997b93171b2
Status: Image is up to date for hello-world:latest
docker.io/library/hello-world:latest
What's Next?
  1. Sign in to your Docker account → docker login
  2. View a summary of image vulnerabilities and recommendations → docker scout quickview hello-world

% docker tag hello-world localhost:5000/hello-world

% docker push localhost:5000/hello-world
Using default tag: latest
The push refers to repository [localhost:5000/hello-world]
12660636fe55: Pushed 
latest: digest: sha256:a8ea96bb64d60208d6a56712042d1cf58aa4a7d3751b897b9320b0813c81cbb4 size: 524

Relevant log output

No response

Additional Information

I cannot use the latest version of testcontainers since the version is defined by the spring boot starter org.springframework.boot:spring-boot-testcontainers.

pfilaretov42 avatar Oct 07 '24 14:10 pfilaretov42

And one more thing I forgot to mention: registry's catalog is actually available via http:

% curl http://localhost:57025/v2/_catalog
{"repositories":[]}

pfilaretov42 avatar Oct 08 '24 07:10 pfilaretov42

In order to override the version provided by spring boot look at the spring boot gradle and maven docs.

eddumelendez avatar Oct 08 '24 22:10 eddumelendez

In order to override the version provided by spring boot look at the spring boot gradle and maven docs.

Okay, I was not very clear. I know I can override the version, but it should not be done in most cases. Spring docs that you mentioned warns about this:

Each Spring Boot release is designed and tested against a specific set of third-party dependencies. Overriding versions may cause compatibility issues and should be done with care.

pfilaretov42 avatar Oct 09 '24 07:10 pfilaretov42

I've just tried to set the latest testcontainers version explicitly in build.gradle with

ext['testcontainers.version'] = '1.20.2'

The result and errors are the same, I cannot push to the registry started in testcontainers.

pfilaretov42 avatar Oct 09 '24 07:10 pfilaretov42

were you able to find a solution to this issue? I'm also facing the same

sainsaji avatar Feb 24 '25 06:02 sainsaji

no, I haven't found a solution

pfilaretov42 avatar Feb 25 '25 07:02 pfilaretov42

Hi, according to the docs, addr must be configured for The address for which the server should accept connections. Testcontainers starts the server in a random port, so, this must be configured.

The following code make it work

class RegistryContainer : GenericContainer<RegistryContainer>("registry:2") {

        companion object {
            private val STARTER_SCRIPT: String = "/tmp/testcontainers_start.sh"

            private val COMMAND: Array<String> = arrayOf(
                "-c",
                "while [ ! -f $STARTER_SCRIPT ]; do sleep 0.1; done; $STARTER_SCRIPT"
            )
        }
        
        init {
            withExposedPorts(5000)
            withCommand(*COMMAND)
            withCreateContainerCmdModifier({ cmd -> cmd.withEntrypoint("/bin/sh") })
            waitingFor(Wait.forLogMessage(".*listening on.*", 1))
        }

        override fun containerIsStarting(containerInfo: InspectContainerResponse?) {
            val command = """
                #!/bin/sh
                REGISTRY_HTTP_ADDR=$host:${getMappedPort(5000)}
                /entrypoint.sh /etc/docker/registry/config.yml
            """.trimIndent()
            
            copyFileToContainer(Transferable.of(command, Integer.parseInt("777", 8)), STARTER_SCRIPT)
        }
        
    }

eddumelendez avatar Mar 12 '25 02:03 eddumelendez