Control when Docker images are pulled from registries. Image pull policies determine whether Testcontainers should use locally cached images or fetch fresh copies from the registry, enabling flexible performance tuning and ensuring tests use appropriate image versions.
Testcontainers supports multiple image pull strategies to balance between:
import org.testcontainers.images.ImagePullPolicy;
import org.testcontainers.utility.DockerImageName;
/**
* Interface for defining when images should be pulled from a registry.
* Implement this interface to create custom pull policies.
*/
public interface ImagePullPolicy {
/**
* Determine if an image should be pulled from the registry.
*
* @param imageName the Docker image name to check
* @return true if the image should be pulled, false to use cached version
*/
boolean shouldPull(DockerImageName imageName);
}import org.testcontainers.images.PullPolicy;
import org.testcontainers.images.ImagePullPolicy;
import java.time.Duration;
/**
* Convenience factory class for creating common ImagePullPolicy instances.
* This is a utility class with static methods only and cannot be instantiated.
*/
@UtilityClass
public class PullPolicy {
/**
* Get the default pull policy.
* If configured via testcontainers.properties (pull.policy property),
* returns the configured policy. Otherwise returns DefaultPullPolicy.
* The default policy pulls images only if they don't exist locally.
*
* @return the default or configured ImagePullPolicy
*/
public static ImagePullPolicy defaultPolicy();
/**
* Get a policy that always pulls images from the registry.
* Useful for development with 'latest' tags or when you always
* need the most recent version of an image.
*
* @return AlwaysPullPolicy instance
*/
public static ImagePullPolicy alwaysPull();
/**
* Get a policy that pulls images if they're older than maxAge.
* Checks the image creation date and pulls if the local image
* age exceeds the specified duration.
*
* @param maxAge maximum age before pulling a fresh copy
* @return AgeBasedPullPolicy instance
*/
public static ImagePullPolicy ageBased(Duration maxAge);
}Pulls images only if they don't exist locally. This is the default behavior when no policy is explicitly configured.
Implementation Details (package-private, not for direct use):
The DefaultPullPolicy class is a package-private implementation extending AbstractImagePullPolicy. It implements the shouldPullCached() method to return false, meaning it uses cached images when available. Users should access this policy via PullPolicy.defaultPolicy() rather than instantiating it directly.
Behavior:
When to use:
Example:
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.images.PullPolicy;
import org.testcontainers.utility.DockerImageName;
GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("redis:7.0"))
.withImagePullPolicy(PullPolicy.defaultPolicy())
.withExposedPorts(6379);
// Will use local redis:7.0 if available, otherwise pulls it
container.start();Always pulls images from the registry, even if they exist locally. Ensures you always have the latest version for a given tag.
Implementation Details (package-private, not for direct use):
The AlwaysPullPolicy class is a package-private implementation of ImagePullPolicy. Its shouldPull() method always returns true, causing images to be pulled on every run. Users should access this policy via PullPolicy.alwaysPull() rather than instantiating it directly.
Behavior:
When to use:
latest tags in developmentExample:
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.images.PullPolicy;
import org.testcontainers.utility.DockerImageName;
// Always pull latest nginx, even if it's cached
GenericContainer<?> nginx = new GenericContainer<>(DockerImageName.parse("nginx:latest"))
.withImagePullPolicy(PullPolicy.alwaysPull())
.withExposedPorts(80);
nginx.start();Warning: This policy can significantly slow down tests as it pulls images on every run. Use sparingly in CI/CD.
Pulls images if the local cached version is older than a specified duration. Balances freshness and performance.
Implementation Details (package-private, not for direct use):
The AgeBasedPullPolicy class is a package-private implementation extending AbstractImagePullPolicy. It uses Lombok's @Value annotation and stores a Duration maxAge field. The shouldPullCached() method compares the local image's creation date against the current time and returns true if the age exceeds maxAge. Users should access this policy via PullPolicy.ageBased(Duration) rather than instantiating it directly.
Behavior:
When to use:
Example:
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.images.PullPolicy;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
// Pull if local image is older than 7 days
GenericContainer<?> postgres = new GenericContainer<>(DockerImageName.parse("postgres:15"))
.withImagePullPolicy(PullPolicy.ageBased(Duration.ofDays(7)))
.withExposedPorts(5432);
postgres.start();
// Common durations
PullPolicy.ageBased(Duration.ofHours(24)); // Pull if older than 1 day
PullPolicy.ageBased(Duration.ofDays(7)); // Pull if older than 1 week
PullPolicy.ageBased(Duration.ofMinutes(30)); // Pull if older than 30 minutesBase class for implementing custom pull policies with built-in caching logic.
import org.testcontainers.images.AbstractImagePullPolicy;
import org.testcontainers.images.ImageData;
import org.testcontainers.utility.DockerImageName;
/**
* Abstract base class for image pull policies.
* Handles local image cache checking and delegates to subclasses
* for deciding whether to pull cached images.
*/
public abstract class AbstractImagePullPolicy implements ImagePullPolicy {
/**
* Main shouldPull implementation that handles cache lookup.
* Subclasses should NOT override this method.
*
* @param imageName the image to check
* @return true if image should be pulled
*/
@Override
public boolean shouldPull(DockerImageName imageName);
/**
* Decide whether a locally cached image should be pulled.
* This method is only called when the image exists locally.
*
* @param imageName the image name
* @param localImageData metadata about the local image
* @return true to pull a fresh copy, false to use cached version
*/
protected abstract boolean shouldPullCached(DockerImageName imageName, ImageData localImageData);
}Set a pull policy for a specific container:
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.images.PullPolicy;
import org.testcontainers.utility.DockerImageName;
// Container with default policy
GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:7.0"))
.withImagePullPolicy(PullPolicy.defaultPolicy())
.withExposedPorts(6379);
// Container that always pulls
GenericContainer<?> nginx = new GenericContainer<>(DockerImageName.parse("nginx:latest"))
.withImagePullPolicy(PullPolicy.alwaysPull())
.withExposedPorts(80);
// Container with age-based policy
GenericContainer<?> postgres = new GenericContainer<>(DockerImageName.parse("postgres:15"))
.withImagePullPolicy(PullPolicy.ageBased(java.time.Duration.ofDays(7)))
.withExposedPorts(5432);Configure a global default policy via properties file or environment variable:
~/.testcontainers.properties:
# Use fully qualified class name of a policy implementation
pull.policy=org.testcontainers.images.AlwaysPullPolicy
# Or use custom policy
pull.policy=com.example.MyCustomPullPolicyEnvironment variable:
export TESTCONTAINERS_PULL_POLICY=org.testcontainers.images.AlwaysPullPolicyProgrammatic configuration:
import org.testcontainers.utility.TestcontainersConfiguration;
// Set global policy via configuration file
TestcontainersConfiguration.getInstance()
.updateUserConfig("pull.policy", "org.testcontainers.images.AlwaysPullPolicy");When configured globally, PullPolicy.defaultPolicy() returns the configured policy instead of DefaultPullPolicy.
Create custom pull policies by implementing ImagePullPolicy or extending AbstractImagePullPolicy.
import org.testcontainers.images.ImagePullPolicy;
import org.testcontainers.utility.DockerImageName;
/**
* Custom policy that never pulls images tagged with "snapshot".
* Useful when snapshot images are built locally and shouldn't be pulled.
*/
public class NeverPullSnapshotPolicy implements ImagePullPolicy {
@Override
public boolean shouldPull(DockerImageName imageName) {
// Never pull images tagged with "snapshot"
if (imageName.getVersionPart().contains("snapshot")) {
return false;
}
// For other images, pull if not cached
return !isImageCached(imageName);
}
private boolean isImageCached(DockerImageName imageName) {
// Check if image exists locally (simplified)
return false; // Implement actual cache check
}
}import org.testcontainers.images.AbstractImagePullPolicy;
import org.testcontainers.images.ImageData;
import org.testcontainers.utility.DockerImageName;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
/**
* Custom policy that pulls images only on Mondays.
* Ensures fresh images at the start of each week.
*/
public class WeeklyPullPolicy extends AbstractImagePullPolicy {
@Override
protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) {
// Pull if it's Monday
return LocalDateTime.now().getDayOfWeek() == DayOfWeek.MONDAY;
}
}
// Usage
GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("myapp:latest"))
.withImagePullPolicy(new WeeklyPullPolicy());import org.testcontainers.images.AbstractImagePullPolicy;
import org.testcontainers.images.ImageData;
import org.testcontainers.images.PullPolicy;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
/**
* Hybrid policy: always pull 'latest', age-based for other tags.
*/
public class SmartPullPolicy extends AbstractImagePullPolicy {
private static final Duration MAX_AGE = Duration.ofDays(7);
@Override
protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) {
// Always pull 'latest' tags
if ("latest".equals(imageName.getVersionPart())) {
return true;
}
// For specific versions, use age-based logic
Duration imageAge = Duration.between(
localImageData.getCreatedAt(),
java.time.Instant.now()
);
return imageAge.compareTo(MAX_AGE) > 0;
}
}| Policy | First Run | Subsequent Runs | Network Usage | Freshness |
|---|---|---|---|---|
| Default | Slow | Fast | Low | Medium |
| AlwaysPull | Slow | Slow | High | Highest |
| AgeBased (1 day) | Slow | Fast* | Low-Medium | High |
*Fast until age threshold is reached, then slow for one run
Development:
// Use age-based for a balance of freshness and speed
.withImagePullPolicy(PullPolicy.ageBased(Duration.ofDays(1)))CI/CD:
// Use default policy (images typically pre-pulled or cached)
.withImagePullPolicy(PullPolicy.defaultPolicy())Integration Testing (Nightly Builds):
// Always pull to test against latest dependencies
.withImagePullPolicy(PullPolicy.alwaysPull())Use specific image tags in production tests: Avoid latest tag; use versioned tags (e.g., postgres:15.2) for reproducibility
Default policy for CI/CD: Let CI systems handle image caching; use defaultPolicy() to avoid redundant pulls
Age-based for development: Balance freshness and speed with ageBased(Duration.ofDays(1))
Always pull for troubleshooting: If you suspect image issues, temporarily use alwaysPull() to rule out stale caches
Document custom policies: If implementing custom policies, document the logic clearly for team members
Consider registry rate limits: Be aware of Docker Hub rate limits when using alwaysPull()
Pre-pull in CI pipelines: For faster CI builds, pre-pull images in a setup step before running tests
Problem: Tests use stale images despite expecting fresh versions
Solutions:
// Temporarily use alwaysPull to force refresh
.withImagePullPolicy(PullPolicy.alwaysPull())
// Or remove cached images manually
// docker rmi image:tagProblem: Tests are slow due to frequent image pulls
Solutions:
// Switch to default policy
.withImagePullPolicy(PullPolicy.defaultPolicy())
// Or use age-based with longer duration
.withImagePullPolicy(PullPolicy.ageBased(Duration.ofDays(7)))
// Pre-pull images before running tests
// docker pull image:tagProblem: Docker Hub rate limits preventing image pulls
Solutions:
defaultPolicy() to avoid redundant pullsProblem: Custom policy class not being loaded
Check:
# Verify property name and value in ~/.testcontainers.properties
pull.policy=com.example.MyCustomPullPolicy
# Ensure class is on classpath and has no-arg constructorimport org.testcontainers.containers.GenericContainer;
import org.testcontainers.images.PullPolicy;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.junit.jupiter.api.Test;
import java.time.Duration;
@Testcontainers
public class ImagePullPolicyExample {
// Container with default policy (use cached if available)
@Container
private static final GenericContainer<?> redis = new GenericContainer<>(
DockerImageName.parse("redis:7.0")
)
.withImagePullPolicy(PullPolicy.defaultPolicy())
.withExposedPorts(6379);
// Container that always pulls (useful for 'latest' tags)
@Container
private static final GenericContainer<?> nginx = new GenericContainer<>(
DockerImageName.parse("nginx:latest")
)
.withImagePullPolicy(PullPolicy.alwaysPull())
.withExposedPorts(80);
// Container with age-based policy (pull if older than 7 days)
@Container
private static final GenericContainer<?> postgres = new GenericContainer<>(
DockerImageName.parse("postgres:15")
)
.withImagePullPolicy(PullPolicy.ageBased(Duration.ofDays(7)))
.withExposedPorts(5432)
.withEnv("POSTGRES_PASSWORD", "test");
@Test
public void testContainersWithDifferentPullPolicies() {
// All containers are started with their respective pull policies
assert redis.isRunning();
assert nginx.isRunning();
assert postgres.isRunning();
}
}This comprehensive image pull policy system gives you complete control over when images are fetched from registries, enabling optimal test performance while ensuring appropriate freshness for your specific use case.