skip to content

In Testcontainers, when would you use ImageFromDockerfile instead of a published image?

level: middleimportance: should knowfreq 35%

answer

  1. Build now, or pull something built?
  2. The daemon does the building
  3. Every run pays it again
  4. Cold CI cache is the problem
  5. Publish it once it is shared

basics

~20 s

ImageFromDockerfile makes the Docker daemon build an image from a Dockerfile and files you supply, then hands it to a container. Use it when no published image fits; it costs a build on every run.

solid answer

~50 s

`new GenericContainer<>(new ImageFromDockerfile().withFileFromClasspath("Dockerfile", "fixtures/Dockerfile"))` builds the image at test time and starts a container from it. You can also point at a Dockerfile path with `withDockerfile(Path)`, add build context entries with `withFileFromPath`/`withFileFromClasspath`, or describe the image programmatically with `withDockerfileFromBuilder`. It is the right tool when the fixture you need does not exist as a published image — a service image plus extra tooling, a stub with baked-in data — and when keeping the definition next to the test is genuinely worth it. The cost is real: every run pays a build, and on a CI runner with a cold build cache that can dwarf the test itself. By default the built image is removed when the JVM exits, so nothing accumulates but nothing is shared either. Once several suites need the same fixture, build and publish it in your pipeline and pull it in tests instead.

code

java · 5 lines
java
GenericContainer<?> fixture = new GenericContainer<>(
        new ImageFromDockerfile()
                .withFileFromClasspath("Dockerfile", "fixtures/Dockerfile")
                .withFileFromClasspath("seed.json", "fixtures/seed.json"))
        .withExposedPorts(8080);

go deeper

for a junior

Recall that ImageFromDockerfile builds an image during the test and that a GenericContainer can be created from it.

for a middle

Explain how the build context is supplied and that the image is built by the daemon on every run, then removed when the JVM exits.

for a senior

Weigh per-run build cost and reproducibility against convenience, and move shared fixtures to a published, pinned image.

for a principal

Decide where fixture images live organisation-wide: which are published by pipelines, who owns them, and how their tags are pinned and rotated.

## What it does `ImageFromDockerfile` hands the Docker daemon a build context and a Dockerfile, waits for the build, and yields an image name that a container can run. The build happens where the daemon runs, exactly like a normal build. You supply the context in one of three ways: `withDockerfile(Path)` for a Dockerfile on disk with its surrounding directory; `withFileFromClasspath("Dockerfile", "fixtures/Dockerfile")` and `withFileFromPath(...)` to assemble a context entry by entry; or `withDockerfileFromBuilder { ... }` to describe the image in code with no file at all. The result is passed straight to `GenericContainer`. ## When it earns its place Three cases recur. First, **there is no published image**: you need a service plus an extra client binary, or a stub server with fixture data baked in. Second, **you are testing your own application image** and want the test to exercise the artefact rather than a hand-configured approximation. Third, **the definition belongs with the test**: a tiny fixture image that nobody else consumes is clearer next to the test than published somewhere and versioned separately. ## What it costs A build on every run. With a warm daemon cache and a well-layered Dockerfile that can be near-instant; on a fresh CI runner it is a full build before a single assertion executes, and it is paid by every job. Because the built image is by default removed when the test JVM exits, the image itself is not shared between runs — only the daemon's layer cache mitigates the cost, and ephemeral runners do not have one. There is also a determinism angle. Building at test time means your test result depends on whatever the build resolves *now*: base image tags, package indexes, network reachability. A published, pinned image removes that variable entirely, which is why a flaky test whose root cause is a package mirror is such an annoying way to discover this. ## The alternative, and when to switch Build the fixture image in your pipeline, publish it to your registry with a pinned tag, and have tests pull it. Startup then costs a cached pull instead of a build, and every suite gets the same bytes. Switch as soon as more than one suite needs the image, or as soon as the build appears in your slowest-tests report. Keeping a one-off fixture inline is fine; making the whole organisation rebuild it in every job is not. ## Scope note How you write the Dockerfile — layer ordering, multi-stage builds, cache mounts — is general Docker craft and applies unchanged here. What belongs to this topic is the decision to build inside the test at all, the API that does it, and the price you pay per run. ## Interview framing The distinguishing move is volunteering the trade-off unprompted: "it is right for a fixture that does not exist as an image, and I would move it to a published image the moment a second suite needed it or CI started paying for the build."

  • What signals it is time to publish the fixture image instead of building it in the test?
    A second suite needing the same image, or the build showing up as a meaningful slice of CI time. Publishing gives every consumer identical bytes and turns a per-run build into a cached pull. It also removes a determinism risk: a build resolves base tags and package indexes at test time, so an upstream change can break tests that did not change.
  • Why can building the image inside the test make results less reproducible?
    Because the build runs now, against whatever the base tag and package repositories currently serve. Two runs a week apart can produce different images from an unchanged Dockerfile, so a test can start failing with no code change. A pinned, published image freezes those inputs, which is why long-lived suites drift towards pulling rather than building.

saying these in an interview costs you the question

  • Builds a stock image in tests when a published one exists
  • Ignores that CI runners have a cold build cache
  • Assumes the built image is cached across runs
  • Expects Testcontainers to derive ports or wait strategy from the Dockerfile
  • Keeps an org-wide fixture image inline forever

context