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

  1. Check `docker ps -a` for containers still referencing the image and stop them before cleanup
  2. Verify the image name passed to removeImageCmd actually exists (`docker images`); skip cleanup if it was already removed
  3. Inspect the chained exception in the log line for the Docker daemon's actual rejection reason (conflict, in use, etc.)
  4. 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

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


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)