testcontainers/testcontainers-java · warning
Unable to delete image
Error message
Unable to delete image
What it means
ResourceReaper.removeImage attempts a forced Docker image removal via dockerClient.removeImageCmd. If removal throws for any reason (image in use, not found, daemon error), it only logs a warning with this message rather than failing. The warning means Testcontainers could not clean up an image it registered for deletion, typically during JVM shutdown cleanup.
Solutions
- Check `docker ps -a` for containers still referencing the image and stop them before cleanup
- Verify the image name passed to removeImageCmd actually exists (`docker images`); skip cleanup if it was already removed
- Inspect the chained exception in the log line for the Docker daemon's actual rejection reason (conflict, in use, etc.)
- If this appears at JVM shutdown, it is usually harmless: the daemon's own GC or manual cleanup can remove the image
Example fix
// before: assuming image cleanup always succeeds
dockerClient.removeImageCmd(dockerImageName).withForce(true).exec();
// after: check existence and tolerate already-missing images
if (!imageExists(dockerImageName)) { return; }
try {
dockerClient.removeImageCmd(dockerImageName).withForce(true).exec();
} catch (NotFoundException ignored) { /* already gone */ } Defensive patterns
Strategy: try-catch
Validate before calling
boolean exists = dockerClient.listImagesCmd().exec().stream().anyMatch(i -> i.getRepoTags() != null && Arrays.asList(i.getRepoTags()).contains(dockerImageName));
Try / catch
try { dockerClient.removeImageCmd(name).withForce(true).exec(); } catch (NotFoundException e) { /* already removed */ } catch (Exception e) { log.warn("Image cleanup skipped for {}: {}", name, e.getMessage()); } Prevention
- Stop all containers using an image before removing it
- Check image existence before removal attempts
- Inspect the chained cause in the ResourceReaper warning log
- Don't treat shutdown-hook image cleanup warnings as test failures
When it happens
Trigger: An image registered with the ResourceReaper cannot be force-removed: the image is referenced by another running container, it was already removed, the Docker daemon rejects the delete (e.g. child images exist), or the daemon is unreachable at cleanup time.
Common situations: JVM shutdown hook cleanup racing with other containers using the same image; manual `docker rmi` removal before Testcontainers cleanup; shared images still in use by containers outside Testcontainers; Docker daemon restarting during test teardown.
Related errors
- You should never close the global DockerClient!
- Check failed:
- Requested port ( ) is not mapped
- execInContainer can only be used while the Container is…
- Container startup failed for image
AI-assisted analysis of testcontainers/testcontainers-java@8e549514e3 (2026-09-12).
Data as JSON: /api/errors/1c5c970b6792011b.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/testcontainers/utility/ResourceReaper.java:315
public void unregisterContainer(String identifier) {
registeredContainers.remove(identifier);
}
/**
* @deprecated no longer supported API
*/
@Deprecated
public void registerImageForCleanup(String dockerImageName) {
setHook();
registeredImages.add(dockerImageName);
}
private void removeImage(String dockerImageName) {
LOGGER.trace("Removing image tagged {}", dockerImageName);
try {
dockerClient.removeImageCmd(dockerImageName).withForce(true).exec();
} catch (Throwable e) {
LOGGER.warn("Unable to delete image " + dockerImageName, e);
}
}
void setHook() {
if (hookIsSet.compareAndSet(false, true)) {
// If the JVM stops without containers being stopped, try and stop the container.
Runtime
.getRuntime()
.addShutdownHook(new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, this::performCleanup));
}
}
/**
*
* @deprecated internal API
*/
@Deprecated
public Map<String, String> getLabels() {View on GitHub (pinned to 8e549514e3)