# Fix a Broken Docker Build

Yes. An agent reads the failed CI log, reproduces the Docker build in its sandbox, fixes it, then builds and starts the image and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/fix-docker-build/docker/), in a Project that has your repository.

## What the agent does

The agent starts from the failure, not the Dockerfile. It reads the failed job's log, finds the first error rather than the last line, and works out which image and which stage broke. In a repository like GoogleCloudPlatform/microservices-demo that matters, because there are eleven service images in four languages and the build runs through Skaffold: `.github/workflows/ci-main.yaml` installs Docker Buildx and Skaffold, then runs `skaffold run --default-repo=... --tag=$GITHUB_SHA -p network-policies`, and `skaffold.yaml` maps each image to a context such as `src/frontend`, with `src/cartservice/src` as the exception.

Then it reproduces the build in its sandbox with plain `docker build` on the failing context, so the error shows up without Skaffold, GKE or registry credentials in the way. It reads the Dockerfile stage by stage. Here every builder stage is pinned to a digest and built with `--platform=$BUILDPLATFORM`: Go services on `golang:1.27.0-alpine@sha256:…` and a `gcr.io/distroless/static` runtime, Node services on `node:24.20.0-alpine` and an `alpine:3.24.1` runtime, Python on `python:3.14.7-alpine`, cartservice on the .NET 10 SDK and `runtime-deps` chiseled images, adservice on Eclipse Temurin 25. It notes that `gcr.io/distroless/static` is the one base with no tag or digest, so it can change under the build without a commit.

The usual causes are a base image or package that moved, a dependency file copied after the step that needs it, a build context that is missing a file because of `.dockerignore` (seven services here have one, five do not), a lockfile out of step with its manifest, or a compiler version the code no longer builds with. The agent fixes the cause in the Dockerfile or the code and rebuilds until `docker build` succeeds, recording the image size before (when the old image still builds from the previous commit) and after.

A build that succeeds is not yet proof, so the agent starts the image. For the frontend it runs the container with `PORT` set, since `src/frontend/main.go` reads it, and requests `/_healthz`, which answers `ok`. For a gRPC service such as cartservice on port 7070 it checks that the process stays up and listens on the port its Dockerfile `EXPOSE`s. Then it opens the pull request with the build output and the run check.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "The Docker build in CI failed; the log is linked below. Read the log and find the first error and the image it belongs to. Reproduce the build in the sandbox with docker build on that image's context. Fix the cause in the Dockerfile or the code, without unpinning base images or removing build steps. Build the image again, then start the container and check that it serves: an HTTP health endpoint if it has one, otherwise that the process stays up and listens on its exposed port. Report the image size before and after. Open a pull request with the cause, the fix, the final build output and the run check.\n\nFailed job: {ci_log_url}",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request that changes the smallest thing that fixes the build: a Dockerfile line, a `.dockerignore` entry, a lockfile, or the code a new compiler rejected. In a multi-service repository like Online Boutique it touches only the failing service's directory. The description names the cause in one sentence, quotes the error from the CI log, shows the end of the successful `docker build` output, the image size before and after, and the result of starting the container, such as the `ok` from the frontend's `/_healthz`.

The pull request's own checks run the repository's CI on the fix. Where CI only builds images on pushes to the main branch, as `ci-main.yaml` does here while `ci-pr.yaml` runs only Go and C# unit tests, the description says so, and the sandbox build in the trajectory is the evidence until the merge build runs. The trajectory keeps every build attempt with its output, including the ones that failed on the way to the fix.

Before merging, a reviewer checks that the fix addresses the cause rather than hiding it (a pinned digest replaced by a floating tag fixes nothing for long), that no build step or test was removed to get green, and that the image size did not grow for no reason.

## Run it on every failed image build

Subscribe to GitHub's `workflow_run` webhook and, when a run of your image-building workflow completes with `conclusion: failure`, have your server send this request with the run's URL in `input` and the repository in `resources`. Use the workflow run id as the `Idempotency-Key` header so a redelivered webhook does not start a second fix for the same failure.

Give the API key a webhook URL and Rush POSTs `run.completed` or `run.failed` when the agent's turn ends, with the session URL to read the pull request link from, so your server can post it in the channel where the failure was announced.

## Why run it on Rush?

Reproducing a broken image build on a laptop drags in your local Docker cache, a different CPU architecture than CI, and whatever base images you pulled last month, so the build often fails differently or not at all. The agent on Rush builds in a clean sandbox of its own, reproduces the failure from the log, and keeps every `docker build` with its output, so the pull request carries the before and after, not a guess.

It also checks the part people skip: that the fixed image starts and serves. A green build of an image that crashes on boot is a broken deploy one step later. Because each failure is its own run, two services that break on the same base image bump get two independent fixes, each reviewed on its own.

## Frequently asked questions

### Can it build images for another architecture?

It reproduces the build for the sandbox's own architecture first, which catches nearly every Dockerfile and dependency error. If the failure is specific to another platform, it can use Buildx with QEMU emulation in the sandbox, as microservices-demo's CI does with its Set up QEMU step, at the cost of a slower build.

### Does it need my registry credentials?

No. It builds and runs the image locally in the sandbox and never pushes it. Pushing stays with your CI, which runs on the merged fix. If a base image lives in a private registry, the build needs a pull credential; save it as a secret in a Project and start the run in that Project.

### Will it unpin my base images to make the build pass?

The request tells it not to. If the fix genuinely needs a newer base, it updates the pin to a specific new tag and digest and says why in the pull request, so the build stays reproducible.

### What if the build fails because of the CI runner, not the image?

If the sandbox build succeeds unchanged, the agent says so: the cause is outside the Dockerfile, such as a runner's disk space, an expired registry token or a rate limit on image pulls. It reports what in the log points there and opens no pull request, rather than changing code that was not broken.
