testcontainers/testcontainers-java · error · java.lang.RuntimeException
Can't close DockerClient
Error message
Can't close DockerClient
What it means
ImageFromDockerfile.resolve() builds an image from a Dockerfile and must close the temporary DockerClient in a finally-ish path; if closing the client throws IOException it is rethrown as RuntimeException("Can't close DockerClient"). It usually masks an I/O problem on the underlying docker client transport/streams.
Solutions
- Look at the cause (IOException) and earlier logs — the real failure is usually the image build itself, not the close
- Ensure the Docker daemon stayed up during the build; retry the build
- Check ulimit/file-descriptor limits if many containers/images are built in one JVM
- Upgrade testcontainers/docker-java to pick up transport close-handling fixes
- Catch and log this in test scaffolding so the original build failure isn't hidden
Example fix
// before
String id = new ImageFromDockerfile().withDockerfileFromBuilder(b -> b.build()).get(); // masked IOException
// after
// fix the underlying build failure (e.g. invalid Dockerfile step) so close() succeeds
String id = new ImageFromDockerfile()
.withDockerfileFromBuilder(b -> b.from("alpine:3.19").run("echo hi").build())
.get(); Defensive patterns
Strategy: try-catch
Validate before calling
// ensure daemon is healthy before a long build
new ProcessBuilder("docker","info").inheritIO().start().waitFor() == 0; Type guard
null
Try / catch
try {
String id = imageFromDockerfile.get();
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("Can't close DockerClient")) {
// the close-time IOException usually masks an earlier build/transport failure
throw new IllegalStateException("Image build failed; see cause: " + e.getCause(), e);
}
throw e;
} Prevention
- Read the cause chain — this error is almost always secondary to a build or transport failure
- Keep the daemon running through long builds (disable sleep/timeout in CI)
- Raise file-descriptor limits when building many images in one JVM
- Keep testcontainers/docker-java updated for transport fixes
When it happens
Trigger: Calling imageId()/resolve() on an ImageFromDockerfile and the docker-java client's close() raises IOException — e.g. the transport/connection to the daemon is already broken after the build or awaitImageId() failed leaving streams in a bad state.
Common situations: Docker daemon dropped the connection during a long image build; build failed earlier causing streams to be closed twice; resource exhaustion (too many open files); abrupt daemon restart.
Related errors
- Failed to get Docker client for
- You should never close the global DockerClient!
- Check failed:
- Requested port ( ) is not mapped
- execInContainer can only be used while the Container is…
AI-assisted analysis of testcontainers/testcontainers-java@8e549514e3 (2026-09-12).
Data as JSON: /api/errors/a5e71f4da3491af9.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/testcontainers/images/builder/ImageFromDockerfile.java:169
transferable.transferTo(tarArchive, destination);
bytesToDockerDaemon += transferable.getSize();
}
tarArchive.finish();
}
log.info("Transferred {} to Docker daemon", FileUtils.byteCountToDisplaySize(bytesToDockerDaemon));
if (bytesToDockerDaemon > FileUtils.ONE_MB * 50) {
log.warn( // warn if >50MB sent to docker daemon
"A large amount of data was sent to the Docker daemon ({}). Consider using a .dockerignore file for better performance.",
FileUtils.byteCountToDisplaySize(bytesToDockerDaemon)
);
}
exec.awaitImageId();
return dockerImageName;
} catch (IOException e) {
throw new RuntimeException("Can't close DockerClient", e);
}
}
protected void configure(BuildImageCmd buildImageCmd) {
buildImageCmd.withTags(Collections.singleton(getDockerImageName()));
this.dockerFilePath.ifPresent(buildImageCmd::withDockerfilePath);
this.dockerfile.ifPresent(p -> {
buildImageCmd.withDockerfile(p.toFile());
dependencyImageNames = new ParsedDockerfile(p).getDependencyImageNames();
if (dependencyImageNames.size() > 0) {
// if we'll be pre-pulling images, disable the built-in pull as it is not necessary and will fail for
// authenticated registries
buildImageCmd.withPull(false);
}
});
this.buildArgs.forEach(buildImageCmd::withBuildArg);View on GitHub (pinned to 8e549514e3)