Branching Workflows¶
One of Contree’s most powerful features is the ability to branch execution flows from a single base filesystem state (a snapshot of all files and directories in a container at a specific point in time). This is similar to Git branches, but for container execution states.
Why Branching Matters¶
Without Contree, running the same operations twice (like installing packages, creating files, or compiling code) requires rebuilding the entire filesystem state from scratch each time. Contree captures the exact filesystem state after each command, making it reproducible and allowing you to branch from that exact state.
Simple Branching Example¶
This example demonstrates creating a parent state with a random value, then branching into multiple child states from that fixed parent:
1base = await client.images.pull("alpine:latest")
2
3child = await base.run(shell='echo "$RANDOM" > /tmp/random.txt', disposable=False)
4print(f"Child created from base, UUID: {child.uuid}\n")
5
6for i, letter in enumerate(["A", "B", "C"], 1):
7 gc = await child.run(
8 shell=f"echo '{letter}' >> /tmp/random.txt && cat /tmp/random.txt",
9 disposable=False,
10 )
11 print(f"Grandchild {i}: {gc.stdout.strip()}")
See run() for full API reference.
1base = client.images.pull("alpine:latest")
2
3child = base.run(shell='echo "$RANDOM" > /tmp/random.txt', disposable=False).wait()
4print(f"Child created from base, UUID: {child.uuid}\n")
5
6for i, letter in enumerate(["A", "B", "C"], 1):
7 gc = child.run(
8 shell=f"echo '{letter}' >> /tmp/random.txt && cat /tmp/random.txt",
9 disposable=False,
10 ).wait()
11 print(f"Grandchild {i}: {gc.stdout.strip()}")
See run() for full API reference.
Key Points:
The
childstate contains a random value that would be different on each execution without ContreeAll three grandchildren start from the exact same
childstate (same random value)Each grandchild branches independently, creating different execution paths
Advanced Branching Patterns¶
For more complex scenarios with multiple branching strategies:
1image = await client.images.pull("alpine:latest")
2print(f"Pulled {image=}")
3
4print("\nExample 1: Different commands from same image")
5result1 = await image.run(shell="echo 'First branch'", disposable=False)
6result2 = await image.run(shell="echo 'Second branch'", disposable=False)
7result3 = await image.run(shell="ls /bin | head -3", disposable=False)
8
9print(f"Branch 1: {result1.stdout=}, {result1.uuid=}")
10print(f"Branch 2: {result2.stdout=}, {result2.uuid=}")
11print(f"Branch 3: {result3.stdout=}, {result3.uuid=}")
12
13print("\nExample 2: Random output command (different each time)")
14random1 = await image.run(shell="od -An -N2 -tu2 /dev/urandom", disposable=False)
15random2 = await image.run(shell="od -An -N2 -tu2 /dev/urandom", disposable=False)
16
17print(f"Random 1: {random1.stdout=}, {random1.uuid=}")
18print(f"Random 2: {random2.stdout=}, {random2.uuid=}")
19
20print("\nExample 3: Chain of operations from different branches")
21base_result = await image.run(shell="echo 'apple\nbanana\ncherry' > /tmp/fruits.txt", disposable=False)
22
23sort_result = await base_result.run(shell="sort /tmp/fruits.txt", disposable=False)
24reverse_result = await base_result.run(shell="sort -r /tmp/fruits.txt", disposable=False)
25
26print(f"Base: {base_result.uuid=}")
27print(f"Sorted: {sort_result.stdout=}, {sort_result.uuid=}")
28print(f"Reverse sorted: {reverse_result.stdout=}, {reverse_result.uuid=}")
29
30print("\nExample 4: Same command twice - same UUID")
31same1 = await image.run(shell="echo 'Same command'", disposable=False)
32same2 = await image.run(shell="echo 'Same command'", disposable=False)
33
34print(f"Same 1: {same1.stdout=}, {same1.uuid=}")
35print(f"Same 2: {same2.stdout=}, {same2.uuid=}")
36print(f"UUIDs equal: {same1.uuid == same2.uuid}")
See ContreeImage for full API reference.
1image = client.images.pull("alpine:latest")
2print(f"Pulled {image=}")
3
4print("\nExample 1: Different commands from same image")
5result1 = image.run(shell="echo 'First branch'", disposable=False).wait()
6result2 = image.run(shell="echo 'Second branch'", disposable=False).wait()
7result3 = image.run(shell="ls /bin | head -3", disposable=False).wait()
8
9print(f"Branch 1: {result1.stdout=}, {result1.uuid=}")
10print(f"Branch 2: {result2.stdout=}, {result2.uuid=}")
11print(f"Branch 3: {result3.stdout=}, {result3.uuid=}")
12
13print("\nExample 2: Random output command (different each time)")
14random1 = image.run(shell="od -An -N2 -tu2 /dev/urandom", disposable=False).wait()
15random2 = image.run(shell="od -An -N2 -tu2 /dev/urandom", disposable=False).wait()
16
17print(f"Random 1: {random1.stdout=}, {random1.uuid=}")
18print(f"Random 2: {random2.stdout=}, {random2.uuid=}")
19
20print("\nExample 3: Chain of operations from different branches")
21base_result = image.run(shell="echo 'apple\nbanana\ncherry' > /tmp/fruits.txt", disposable=False).wait()
22
23sort_result = base_result.run(shell="sort /tmp/fruits.txt", disposable=False).wait()
24reverse_result = base_result.run(shell="sort -r /tmp/fruits.txt", disposable=False).wait()
25
26print(f"Base: {base_result.uuid=}")
27print(f"Sorted: {sort_result.stdout=}, {sort_result.uuid=}")
28print(f"Reverse sorted: {reverse_result.stdout=}, {reverse_result.uuid=}")
29
30print("\nExample 4: Same command twice - same UUID")
31same1 = image.run(shell="echo 'Same command'", disposable=False).wait()
32same2 = image.run(shell="echo 'Same command'", disposable=False).wait()
33
34print(f"Same 1: {same1.stdout=}, {same1.uuid=}")
35print(f"Same 2: {same2.stdout=}, {same2.uuid=}")
36print(f"UUIDs equal: {same1.uuid == same2.uuid}")
See ContreeImageSync for full API reference.
Use Cases¶
Branching is particularly useful for:
Testing multiple scenarios: Run different operations from the same starting state
Reproducible randomness: Capture random/non-deterministic operations and branch from them
Parallel execution paths: Execute different workflows from a common checkpoint
Version control for execution: Create branches like Git for different execution flows