This repo tries to help enable two goals for users of testcontainers:
- Faster builds – pull the Testcontainers images in the background so it runs in parallel to JDK, dependency cache and build setup.
- Automatic image bumps – create
Dockerfiles instead of inlining image names in code. So that Dependabot can do its job.
In a nutshell what we are trying to do it to stop waiting for testcontainers, i.e. logs like
: ✔︎ Docker server version should be at least 1.6.0
: Pulling docker image: postgis/postgis:17-3.5. Please be patient; this may take some time but only needs to be done once.
: Starting to pull image
: Pulling image layers: 0 pending, 0 downloaded, 0 extracted, (0 bytes/0 bytes)
...
: Pulling image layers: 0 pending, 18 downloaded, 18 extracted, (208 MB/208 MB)
: Pull complete. 18 layers, pulled in 7s (downloaded 208 MB at 29 MB/s)
so this can save from 5 to 30 seconds per build, depending on the number and size of images. It does not use any cache.
The project has three parts:
- GitHub Action – pre-pulls images from Dockerfile(s), in the background. Not a workflow.
- Small Dockerfile parser – extracts image names from Dockerfile(s) for passing on to testcontainers.
- Dependabot config example – Copy-paste ready
Create a directory with very simple Dockerfiles, preferably
Dockerfile.myapp
e.g. src/test/resources/testcontainers/Dockerfile.redis:
FROM redis:7.2or with digest
FROM redis@sha256:...The action reads only the first FROM line, in the form FROM image[:tag][@digest].
It does not build Dockerfiles or support builders/stage aliases (AS), flags, build args or inline comments.
Call pre-pull-docker-images right after checkout, before JDK and caches are restored:
- uses: actions/checkout@v7
- name: Pre-pull Docker images in background
uses: entur/gha-testcontainers/.github/actions/pre-pull-docker-images@v1
with:
dockerfile-paths: src/test/resources/testcontainers
- name: Set up Java 25
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
java-version: 25
distribution: liberica
...or the equivalent for Gradle.
| Input | Required | Description |
|---|---|---|
dockerfile-paths |
yes | Whitespace/newline-separated paths searched recursively for files named Dockerfile*. |
registry |
no | Registry address to log in to (e.g. ghcr.io). Default docker.io. |
registry-login |
no | Registry username. Login only happens if both login and password are set. |
registry-password |
no | Registry password or access token. |
prepare-report |
no | true records each pull's output, exit code and duration for the report action. Default false. |
| Output | Description |
|---|---|
images |
Newline-separated list of images that were pre-pulled. |
Pulls run detached - they continue in the background after the step ends. Their output is discarded unless you set prepare-report: true, see Reports.
Background pulls are silent: if one fails (typo in a tag, missing credentials, rate limit), Testcontainers just pulls the image
itself later and nothing tells you the pre-pull was wasted. Set prepare-report: true and add the pre-pull-docker-images-report
action to see, per image, whether it was pulled, failed or was still running, how long it took, and the tail of its log:
- uses: actions/checkout@v7
- uses: entur/gha-testcontainers/.github/actions/pre-pull-docker-images@v1
with:
dockerfile-paths: src/test/resources/testcontainers
prepare-report: true
# ...
# your steps here, e.g. set up Java and restore dependency caches
# ...
- uses: entur/gha-testcontainers/.github/actions/pre-pull-docker-images-report@v1
- run: mvn verify
- if: always()
uses: entur/gha-testcontainers/.github/actions/pre-pull-docker-images-report@v1The report is printed in the job log (one group per image) and added to the job summary; each report step adds its own section. Use the same version of both actions. Composite actions cannot run a step at the end of the job by themselves, which is why the report is a separate step.
| Input | Required | Description |
|---|---|---|
fail-on-error |
no | true fails the step if a pull has failed. Pulls still running never fail it. Default false. |
This repository's example workflow builds the Maven example with and without the pre-pull.
Add a parser to get the right image into testcontainers. Alternatively write your own.
// classpath resource: src/test/resources/testcontainers/Dockerfile.redis
String image = DockerfileImages.fromResource("testcontainers/Dockerfile.redis");
var redis = new GenericContainer<>(DockerImageName.parse(image));Get our parser from no.entur.testcontainers:dockerfile-helper:1.0.1, alternatively directly copy the class DockerfileImages.java.
Dependabot's docker ecosystem understands FROM lines in files named Dockerfile*, so it bumps the
tag or digest in your test Dockerfiles with no extra tooling. Add to .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "docker"
directory: "/src/test/resources/testcontainers"
schedule:
interval: "weekly"
cooldown:
default-days: 7
groups:
testcontainers:
patterns:
- "*"
ignore:
- dependency-name: "bitnamisecure/redis"directorymust match where the Dockerfiles live (same as the action'sdockerfile-paths) and starts with/. It is not recursive: add one entry per directory (or usedirectories:with globs).- Dependabot picks up files whose name contains
Dockerfile(e.g.Dockerfile.redis), soDockerfile.<name>works out of the box. groupscollects all image bumps into a single PR;cooldowndelays brand-new releases.ignoreis for images deliberately pinned (e.g. a known-badlatest); add a comment in the Dockerfile explaining why.- Digest-pinned images (
FROM image@sha256:...) are bumped too, as long as a tag is present (FROM image:1.2@sha256:...) - without a tag Dependabot has nothing to track.
See examples/maven/.github/dependabot.yaml.
The action needs no GITHUB_TOKEN permissions and no secrets: pulling public images is anonymous.
- Only pass
registry-login/registry-passwordif you need private images or higher rate limits, and then use a read-only pull token, not a personal password. Forghcr.io,github.actorwithsecrets.GITHUB_TOKENandpackages: readavoids a stored secret altogether. - Credentials reach the login via environment variables and
--password-stdin(never on a command line or interpolated into the script), and are written to the runner's Docker config for the lifetime of the job, as the background pulls need them. Use ephemeral/GitHub-hosted runners, or log out yourself on shared runners. - Dockerfile content is treated as untrusted (e.g. from a pull request). A stateful character whitelist allows
letters, digits and
. _ / : - [ ]in image names/tags, letters, digits and_ + . -in digest algorithms, and only hexadecimal characters in the digest. References cannot start with an option or contain shell syntax, whitespace or control characters. This is not a full Docker/OCI grammar validator. Logged values have control characters stripped, theimagesoutput uses a random delimiter, and images are passed todocker pull --so they can never be parsed as options. - The action does not write files into the checkout. Collected image lists are stored under
$RUNNER_TEMP, and background pull output goes to/dev/null, so repository symlinks cannot redirect pull logs into runner files. Withprepare-report: true, pull output instead goes to a directory under$RUNNER_TEMP(refused if it is a symlink or not owned by the runner user), in files named after a hash of the image, never after the image itself. The report action treats that output as untrusted: only printable ASCII is shown, every log line is prefixed so it cannot form a workflow command (::...) or close the summary's code fence, and only the tail is shown. - Pin the action to a commit SHA for supply-chain safety.