How to Mock Multi-Stage Docker Builds in Python

Simulate a multi-stage Docker build in pure Python using classes and temp directories to understand how build stages copy artifacts into a final image.

Easy Python 3.9+ Aug 9, 2026 Production deployment patterns 14 views 0 copies

Python code

61 lines
Python 3.9+
# Simulate multi-stage Docker build with pure Python
from pathlib import Path
import tempfile
import shutil

class BuildContext:
    """Mimics a Docker build context with stages."""
    
    def __init__(self, name):
        self.name = name
        self.files = {}
    
    def add_file(self, dest, content):
        self.files[dest] = content
    
    def stage(self, stage_name, target_dir):
        """Simulate a build stage that copies files to an image layer."""
        print(f"--- Stage: {stage_name} ({self.name}) ---")
        stage_path = Path(target_dir) / stage_name
        stage_path.mkdir(parents=True, exist_ok=True)
        for dest, content in self.files.items():
            file_path = stage_path / dest
            file_path.parent.mkdir(parents=True, exist_ok=True)
            file_path.write_text(content)
            print(f"  COPY {content.splitlines()[0][:20]:<20} → {dest}")
        return stage_path

def multi_stage_docker_mock():
    """Build a minimal app in 2 stages, copy only artifacts to final."""
    with tempfile.TemporaryDirectory() as build_root:
        root = Path(build_root)
        
        # Stage 1: install dependencies / compile (simulated)
        builder = BuildContext("builder")
        builder.add_file("app.py", "def main():\n    print('Hello from compiled app')\n\nif __name__ == '__main__':\n    main()")
        builder.add_file("requirements.txt", "requests==2.31.0\nflask==2.3.3")
        builder.stage("deps", build_root)
        
        # Stage 2: final runtime image — only copy the app, not build deps
        final = BuildContext("final")
        final.add_file("app.py", "def main():\n    print('Hello from final image')\n\nif __name__ == '__main__':\n    main()")
        final.stage("runtime", build_root)
        
        # Show final image contents (simulating `docker run`)
        runtime_dir = root / "runtime"
        print("\nFinal image contents:")
        for f in runtime_dir.rglob("*"):
            if f.is_file():
                print(f"  /{f.relative_to(runtime_dir)}")
        
        # Execute the app (like running the container)
        import subprocess
        result = subprocess.run(
            ["python", str(runtime_dir / "app.py")], 
            capture_output=True, text=True
        )
        print(f"\nContainer run output: {result.stdout.strip()}")

if __name__ == "__main__":
    from shutil import rmtree  # just to keep imports clean
    multi_stage_docker_mock()

Output

stdout
--- Stage: deps (builder) ---
  COPY def main():
  → app.py
  COPY requests==2.31
  → requirements.txt
--- Stage: runtime (final) ---
  COPY def main():
  → app.py

Final image contents:
  /app.py

Container run output: Hello from final image

How it works

The BuildContext class mimics a Docker build context by storing files as a dict and a stage method that copies them into a target directory, creating directory layers like an image. Stages are simulated with tempfile.TemporaryDirectory, which cleans up automatically after the build completes. The final stage only includes app.py, showing how multi-stage builds keep the runtime image lean by excluding build dependencies. Running the final artifact with subprocess simulates a container execution to validate that only the copied code runs. This pattern is useful for debugging build logic or teaching Docker concepts without a Docker daemon.

Common mistakes

  • Importing `rmtree` inside the main block — it's unused and should be removed for clarity.
  • Assuming the simulated stages actually install dependencies — the code only copies files, it doesn't emulate `pip install`.
  • Not using `exist_ok=True` in `mkdir`, which would crash if a stage directory already exists.

Variations

  1. Use `pathlib.Path.write_bytes` with binary content for non-text build artifacts.
  2. Replace the print statements with a list of copied paths to build a dependency graph.

Real-world use cases

  • Teaching new team members how multi-stage Docker builds separate build-time deps from runtime artifacts using a runnable example.
  • Testing deployment scripts without a Docker daemon in CI by simulating stage file copying before real container builds.
  • Generating a build manifest from staged file lists to audit what ends up in each image layer.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Production deployment patterns

Related tutorials and quizzes for this topic.