or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcontainer-lifecycle.mddocker-client.mddocker-compose.mdimage-building.mdimage-management.mdimage-pull-policies.mdindex.mdjunit-jupiter-integration.mdlifecycle.mdmodules-overview.mdnetwork-configuration.mdoutput-handling.mdstartup-checks.mdutility-classes.mdwait-strategies.md
tile.json

image-pull-policies.mddocs/

Image Pull Policies

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.

Overview

Testcontainers supports multiple image pull strategies to balance between:

  • Performance: Avoiding unnecessary network calls by using cached images
  • Freshness: Ensuring tests use the latest image versions
  • Bandwidth: Minimizing registry bandwidth usage in CI/CD environments
  • Reproducibility: Controlling when images are updated

ImagePullPolicy Interface

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);
}

PullPolicy Factory

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);
}

Built-in Pull Policies

DefaultPullPolicy

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:

  • Image exists locally → Use cached version
  • Image doesn't exist locally → Pull from registry
  • Image tag changes → Pull new version

When to use:

  • CI/CD environments where images are pre-pulled
  • Development with specific version tags
  • When network bandwidth is limited
  • When reproducibility is important

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();

AlwaysPullPolicy

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:

  • Always returns true → Always pulls from registry
  • Ignores local cache completely
  • Ensures you have the absolute latest version

When to use:

  • Working with latest tags in development
  • Testing against nightly builds or rolling releases
  • Debugging suspected image caching issues
  • When you need guaranteed fresh images every time

Example:

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.

AgeBasedPullPolicy

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:

  • Image doesn't exist locally → Pull from registry
  • Image exists and age < maxAge → Use cached version
  • Image exists and age >= maxAge → Pull fresh copy

When to use:

  • Development environments (e.g., pull if older than 24 hours)
  • Testing against regularly updated base images
  • Balancing freshness and network bandwidth
  • Long-running test suites where images might become stale

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 minutes

AbstractImagePullPolicy

Base 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);
}

Using Pull Policies

Container-Level Policy

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);

Global Policy Configuration

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.MyCustomPullPolicy

Environment variable:

export TESTCONTAINERS_PULL_POLICY=org.testcontainers.images.AlwaysPullPolicy

Programmatic 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.

Custom Pull Policies

Create custom pull policies by implementing ImagePullPolicy or extending AbstractImagePullPolicy.

Simple Custom Policy

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
    }
}

Advanced Custom Policy (Extending AbstractImagePullPolicy)

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());

Conditional Policy Based on Tag

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;
    }
}

Performance Considerations

Impact on Test Execution Time

PolicyFirst RunSubsequent RunsNetwork UsageFreshness
DefaultSlowFastLowMedium
AlwaysPullSlowSlowHighHighest
AgeBased (1 day)SlowFast*Low-MediumHigh

*Fast until age threshold is reached, then slow for one run

Recommendations by Environment

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())

Best Practices

  1. Use specific image tags in production tests: Avoid latest tag; use versioned tags (e.g., postgres:15.2) for reproducibility

  2. Default policy for CI/CD: Let CI systems handle image caching; use defaultPolicy() to avoid redundant pulls

  3. Age-based for development: Balance freshness and speed with ageBased(Duration.ofDays(1))

  4. Always pull for troubleshooting: If you suspect image issues, temporarily use alwaysPull() to rule out stale caches

  5. Document custom policies: If implementing custom policies, document the logic clearly for team members

  6. Consider registry rate limits: Be aware of Docker Hub rate limits when using alwaysPull()

  7. Pre-pull in CI pipelines: For faster CI builds, pre-pull images in a setup step before running tests

Troubleshooting

Images Not Updating

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:tag

Slow Test Execution

Problem: 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:tag

Registry Rate Limits

Problem: Docker Hub rate limits preventing image pulls

Solutions:

  • Use defaultPolicy() to avoid redundant pulls
  • Configure Docker Hub authentication
  • Use a private registry or mirror
  • Implement custom policy that respects rate limits

Custom Policy Not Working

Problem: 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 constructor

Complete Example

import 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.