Repository navigation
Include the build output when an image build fails - #1511
Draft
cristianrgreco wants to merge 2 commits into
Draft
cristianrgreco wants to merge 2 commits into
cristianrgreco wants to merge 2 commits into
Conversation
A failed `fromDockerfile(...).build()` threw only "Failed to build image". The error now carries the daemon's error and the last 50 lines of build output, for the classic builder and BuildKit. Builds the daemon rejects before streaming now reject instead of never settling.
…output-cde11c # Conflicts: # packages/testcontainers/src/container-runtime/clients/image/docker-image-client.ts
cristianrgreco
marked this pull request as draft
October 13, 2026 15:55
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When
GenericContainer.fromDockerfile(...).build()fails, the error now says why:DockerImageClient.build()reads the build stream into aBuildOutput, which keeps the daemon'serrorentry and the last 50 non-empty lines of output (ANSI codes stripped), and throws when the daemon reported an error. Only the tail is held in memory.moby.buildkit.traceentries (Container build logs aren't very useful because they're encoded #1049).buildkit-trace.tsreads the vertex names and log messages out of them with a small wire-format reader, so no schema or dependency is added. If this is not wanted, deleting that file and its one call site leaves BuildKit errors with the daemon's error line only.GenericContainerBuilder.build()prefixes the failure withFailed to build image:and keeps the original error ascause.build()never settled. It now rejects, so the skippedshould throw an error for a target stage that does not existtest is enabled.docs/features/images.md.Docker Compose builds are not changed: they run through the
docker composeCLI incompose-client.ts. Its error carries the CLI's stderr, which names the failing Dockerfile line and exit code but not the step's own output (the CLI writes that to stdout). That would be a separate change.Other implementations
BuildImageResultCallbackthrowsCould not build image: <error>from the stream'serrorentry, and a bareCould not build imagewhen there is none.jsonmessage.DisplayStreamreturns the stream's error, wrapped asbuild image: <error>.ImageBuildFailedExceptionlists the stream'serrorentries; its BuildKit path shells out todocker buildx buildand puts the CLI's stderr in the exception.BuildError(reason, build_log).Borrowed: the error entry in the message with a fixed prefix and a bare fallback (Java, Go, .NET), and including build output (.NET BuildKit, Python). The bounded tail is specific to this PR.
Verification
npm run formatnpm run lintnpm run knipnpm run check-compilesnpx vitest run packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts packages/testcontainers/src/container-runtime/clients/image/build-output.test.tsnpx vitest run packages/testcontainers/src/generic-container/generic-container.test.ts -t "target stage that does not exist"Test results
Red, before the implementation change:
Green, after:
Not run locally against Podman; the Podman compat build handler streams step output as
streamentries and the failure as anerrorentry, which is the shape this change reads.Not breaking
ImageClient.build()still returnsPromise<void>.Failed to build image, and the bare message is still thrown when the daemon reports no error but the image is missing.client.image.build()now rejects when the daemon reports a build error (it used to resolve and leave the caller to notice the missing image), other errors from it are now prefixed withFailed to build image:byGenericContainerBuilder.build()with the original ascause, and pre-stream rejections surface instead of hanging.Closes #1508