Tickd.ai
← The Tickd Guide

Tutorials & Guides

How to Build a Self-Healing Dockerfile Generator Using Claude 3.5 Sonnet and Python

Stop wasting time copy-pasting cryptic Linux build errors. Learn how to build a Python harness that uses Claude 3.5 Sonnet to automatically compile, debug, and repair failing Docker builds in real time.

Updated 10/1/2026

The Infinite Loop of Broken Builds

We have all been there. You grab a legacy project or start a fresh Node/Python application, write a basic Dockerfile, and run docker build.

Then, the wheels fall off.

Maybe Alpine is missing a compile-time dependency like make or g++. Maybe a Python library requires a specific system header. Or maybe a Node package is screaming about incompatible architecture. Your workflow grinds to a halt as you copy the raw compiler error, paste it into an LLM, grab the suggested fix, rewrite the Dockerfile, and try again.

This is manual labour masquerading as engineering. We can do better. By wrapping our build engine in a lightweight Python harness and calling on the reasoning capabilities of Claude 3.5 Sonnet, we can build a self-healing pipeline that runs, analyses, patches, and rebuilds our containers until they compile successfully.

It’s this automated debugging loop that makes the whole machine tick.

The Architecture of a Self-Healing Build

Our tool is straightforward but incredibly robust. It executes three steps in a loop:

  1. Run: Execute the Docker build command programmatically and capture both stdout and stderr in real time.
  2. Inspect: If the build succeeds, we exit. If it fails, we extract the terminal output, isolate the specific step that threw the error, and compile the context.
  3. Heal: We ship the failing Dockerfile and the error trace to Claude using the Anthropic API. Claude patches the Dockerfile, writes it back to disk, and we restart the loop.

To make this production-ready, we will enforce strict structured output using Claude’s system prompts to ensure we receive valid code blocks rather than conversational filler. If you run into issues setting up your environment, check our guide on troubleshooting Claude API workflows.

Step 1: Setting Up the Python Execution Harness

First, let’s construct the execution module. We need a way to run docker build and capture output without locking up the terminal or ignoring exit codes. We will use Python’s native subprocess library to achieve this.

Create a file named healer.py and add the following bootstrap code:

`python import subprocess import sys from pathlib import Path

def run_docker_build(tag: str = "self-healed-app") -> tuple[bool, str]: """Runs the Docker build command and returns a (success_boolean, output_string) tuple.""" print(f"Executing: docker build -t {tag} .") # We run with --no-cache to ensure we hit any hidden network or install errors process = subprocess.Popen( ["docker", "build", "--no-cache", "-t", tag, "."], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) stdout_accum = [] stderr_accum = [] # Stream output to terminal so the user can see what is happening while True: output = process.stdout.readline() error = process.stderr.readline() if output == '' and error == '' and process.poll() is not None: break if output: sys.stdout.write(output) stdout_accum.append(output) if error: sys.stderr.write(error) stderr_accum.append(error) return_code = process.poll() combined_output = "".join(stdout_accum) + "\n" + "".join(stderr_accum) return return_code == 0, combined_output `

This function handles the execution layer. It streams output directly to the terminal while caching the complete console trace so we can feed it to our model if something breaks.

Step 2: Designing the Healing Prompt

When a build fails, we need Claude to act as a seasoned DevOps engineer. We don't want a long-winded explanation of why Alpine packages differ from Debian packages; we just want a working Dockerfile.

To ensure we do not run into parsing errors, we will instruct Claude to return the modified file inside an XML block. We can then extract this programmatically without needing complex regex or heavy framework dependencies.

Here is our system prompt:

`python SYSTEM_PROMPT = """ You are an expert systems engineer specialising in Docker, Linux package managers, and container optimisation. Your job is to fix failing Dockerfile builds.

You will be provided with: 1. The current contents of the Dockerfile. 2. The full terminal output from a failed docker build attempt.

Analyze the error message carefully. Identify missing system packages, broken dependency trees, incorrect pathing, or outdated environment variables.

Your output must be strictly in the following format: <analysis>Provide a brief, 2-sentence summary of what went wrong and how you are fixing it.</analysis> <dockerfile> Insert the complete, corrected Dockerfile here. Do not omit any lines. </dockerfile>

Do not include any other conversational text or markdown code blocks outside of these XML tags. """ `

Step 3: Integrating the Anthropic API

Next, install the official Anthropic client package if you haven't already:

`bash pip install anthropic `

Now, let's write the module that interfaces with Claude 3.5 Sonnet. We will parse the XML tags from the response using simple Python string manipulation.

`python import os from anthropic import Anthropic

def call_claude_to_heal(dockerfile_content: str, build_log: str) -> tuple[str, str]: """Sends the failing files to Claude and extracts the repaired Dockerfile.""" api_key = os.environ.get("ANTHROPIC_API_KEY") if not api_key: raise ValueError("Please set the ANTHROPIC_API_KEY environment variable.") client = Anthropic(api_key=api_key) user_message = f""" === CURRENT DOCKERFILE === {dockerfile_content} === FAILED BUILD LOG === {build_log} """ response = client.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=4000, system=SYSTEM_PROMPT, messages=[ {"role": "user", "content": user_message} ], temperature=0.1 # Low temperature ensures deterministic debugging choices ) raw_text = response.content[0].text # Parse the XML blocks try: analysis = raw_text.split("<analysis>")[1].split("</analysis>")[0].strip() healed_dockerfile = raw_text.split("<dockerfile>")[1].split("</dockerfile>")[0].strip() return analysis, healed_dockerfile except IndexError: print("Error parsing Claude's response. Raw response:") print(raw_text) sys.exit(1) `

Step 4: Building the Main Execution Loop

Now we tie our two components together in a main execution loop. We'll set a hard limit of 5 correction attempts to prevent infinite API spend if we hit a wall.

`python def main(): dockerfile_path = Path("Dockerfile") if not dockerfile_path.exists(): print("Error: No Dockerfile found in the current directory.") sys.exit(1) max_attempts = 5 for attempt in range(1, max_attempts + 1): print(f"\n=== Attempt {attempt} of {max_attempts} ===") # 1. Run the build success, log = run_docker_build() if success: print("\n🎉 Success! The Docker image built successfully.") break print("\n❌ Build failed. Initiating self-healing protocol...") # 2. Read the failing Dockerfile with open(dockerfile_path, "r") as f: current_content = f.read() # 3. Call Claude for the patch print("Analysing build logs with Claude 3.5 Sonnet...") analysis, repaired_dockerfile = call_claude_to_heal(current_content, log) print(f"\nDiagnosis: {analysis}") print("Applying patch to Dockerfile...") # 4. Overwrite Dockerfile with the healed version with open(dockerfile_path, "w") as f: f.write(repaired_dockerfile) else: print(f"\nReached maximum limit of {max_attempts} attempts. Healing failed.") sys.exit(1)

if __name__ == "__main__": main() `

Putting It to the Test

To test this, let's create a intentionally broken Dockerfile. Create a standard Dockerfile in the same directory that tries to compile a Node.js binary requiring native compilation, but uses a barebones, unconfigured Alpine image:

`dockerfile FROM node:20-alpine WORKDIR /app COPY package.json . # node-gyp requires python3, make, and g++ to compile native modules RUN npm install bcrypt COPY . . CMD ["node", "index.js"] `

If you run python healer.py, the build will fail immediately at the npm install step.

Watch the console. Your Python script will catch the error, send it to Claude, write the necessary Alpine toolchain installation instructions (apk add --no-cache python3 make g++) into the Dockerfile, and rebuild. Within two iterations, you will have a perfectly functional, cached, and compiled Docker build.

Going Further: Production Considerations

While this local implementation works brilliantly for individual developer workflows, running automated self-healing scripts in continuous integration pipelines requires safety checks. You can read up on the structural foundations of handling models and standard structures in our glossary.

If you deploy this pattern in CI, ensure that you: - Pin your versions: Ensure Claude does not randomly upgrade OS versions unless required to solve the dependency conflict. - Use local caching: Keep local caches of build layers to prevent your runner from hitting external package registries repeatedly while testing patches. - Report changes: Integrate a step that automatically creates a git diff or pulls a PR branch containing the healed Dockerfile so your human engineers can review the changes before merging to main.

claudepythondockerdevopsautomation

Keep going

Build something with the prompt generator, decode the jargon in the glossary, or compare the tools on our platform deep-dives.