Layered Snapshot Builds with Infinibranch
Snapshot.build(...)
from the morphcloud.api
module lets you treat Morph Cloud snapshots like Docker layers. You describe a sequence of effects—shell commands or Python callables—and Morph Cloud snapshots the machine after each step. The SDK automatically reuses the longest cached prefix (via Infinibranch snapshots), which means subsequent runs skip straight to the first new step.
This guide walks through two caching-heavy workflows:
- Pulling a remote version number at runtime to decide whether a cache should be reused or expanded.
- Spawning multiple branch-specific snapshots that share a long common prefix so you can roll back experiments instantly.
Prerequisites
- Install the SDK:
pip install morphcloud
- Set the
MORPH_API_KEY
environment variable. - Have a "builder" snapshot ID you want to extend—typically a base OS image or an application bootstrap snapshot.
Understand Snapshot.build
from morphcloud.api import MorphCloudClient
client = MorphCloudClient()
base = client.snapshots.get("snapshot_base_id")
recipe = [
"apt-get update && apt-get install -y curl jq", # shell command step
lambda inst: inst.exec(["bash", "-lc", "mkdir -p /opt/app"]), # python callable step
]
final_snapshot = base.build(recipe)
print(final_snapshot.id)
Each entry in recipe
becomes a layer:
- Strings run as foreground shell commands (
instance.exec
behind the scenes). - Callables receive the running
Instance
object so you can use Python for richer logic (file uploads, API calls, conditional behavior).
When you rerun base.build(recipe)
the SDK checks, step by step, for existing snapshots with the same digest. Matching layers are skipped and the cached snapshot at the end of the prefix becomes the new starting point.
Treat your step list like a Dockerfile. Put the most stable layers first, and reserve the last steps for volatile configuration so the expensive work stays cached.
Use case A — Cache Busting with Remote Version Numbers
Imagine a CLI bundle published at https://releases.example.com/mycli/latest.json
. The JSON looks like { "version": "1.24.0", "tarball": "https://.../mycli-1.24.0.tar.gz" }
. You want a snapshot that always contains the newest CLI, but only rebuilds when the version changes.
import os
import requests
from morphcloud.api import MorphCloudClient
client = MorphCloudClient(api_key=os.environ["MORPH_API_KEY"])
builder = client.snapshots.get("snapshot_builder_id")
release = requests.get(
"https://releases.example.com/mycli/latest.json", timeout=5
).json()
version = release["version"]
tarball_url = release["tarball"]
steps = [
"apt-get update && apt-get install -y curl tar", # stays cached for months
lambda inst: inst.exec("mkdir -p /opt/mycli"),
# The command embeds the resolved version: if the version changes, so does the digest.
f"curl -fsSL {tarball_url} -o /opt/mycli/mycli-{version}.tar.gz",
f"tar -xzf /opt/mycli/mycli-{version}.tar.gz -C /opt/mycli",
f"ln -sfn /opt/mycli/mycli-{version} /usr/local/bin/mycli",
]
snapshot = builder.build(steps)
print(f"Snapshot {snapshot.id} now carries mycli {version}")
Why this works
- The remote version is resolved before you build. Including the
version
string inside the command means the step digest changes whenever a new release ships. - If
version
is unchanged, the digest matches a cached snapshot and the build skips the download and extraction entirely. - When the version bumps, only the download and extraction steps rerun; everything before them (OS updates, directory scaffolding) remains cached.
Optional: Guard rails with callable steps
You can wrap the version download in a callable to add validation or fallback logic using the same Instance.exec
API that backs normal shell steps:
from functools import partial
def ensure_supported_release(inst, version: str):
resp = inst.exec([
"bash",
"-lc",
f"grep -q '{version}' /opt/mycli/compat-matrix.txt || exit 42",
])
if resp.exit_code == 42:
raise RuntimeError(f"{version} is not yet approved for production")
steps.insert(2, partial(ensure_supported_release, version=version))
If the callable raises an exception, Snapshot.build
stops the instance and surfaces the traceback, so failed builds behave just like a broken Docker layer.
Use case B — Branch Rollbacks with Shared Prefixes
Suppose your product lets teams live-edit a "vibe coding" workspace. You keep a golden base snapshot with build tools, and on demand you branch into customer-specific variants (e.g., main
, feature/darker-theme
, rollback/v1.3
). The majority of the setup is identical—only the final git checkout
and configuration diverge.
COMMON_STEPS = [
"apt-get update && apt-get install -y git nodejs", # heavy work cached once
"mkdir -p /var/vibe",
"git clone https://github.com/example/vibe-app.git /var/vibe/src",
"cd /var/vibe/src && npm ci",
]
BRANCH_STEP = "cd /var/vibe/src && git checkout {branch}"
CONFIG_STEP = "cd /var/vibe/src && npm run build"
from morphcloud.api import MorphCloudClient
client = MorphCloudClient()
seed = client.snapshots.get("snapshot_seed_id")
# Prime the cache (happens once) so the heavy dependency work is frozen.
base = seed.build(COMMON_STEPS)
branches = ["main", "feature/darker-theme", "rollback/v1.3"]
results = {}
for branch in branches:
steps = [
BRANCH_STEP.format(branch=branch),
CONFIG_STEP,
f"echo 'branch={branch}' > /var/vibe/.active-branch",
]
snap = base.build(steps)
results[branch] = snap.id
print(f"Prepared {branch} → snapshot {snap.id}")
What happens under the hood
- The first call to
seed.build(COMMON_STEPS)
produces a snapshot whose digest encodes every setup step. Subsequent builds recognize that digest and skip straight to the branch-specific commands. - Switching to
feature/darker-theme
only executes thegit checkout
, build artifacts, and branch marker. The clone, dependency install, and other heavy steps are reused instantly. - Rolling back to
main
later revives the cached snapshot tail forBRANCH_STEP
+CONFIG_STEP
, so the entire environment flips branches in seconds.
Because every branch snapshot inherits from the same cached prefix, you can explore ideas fearlessly—rollback just means pointing your deployment at a different snapshot ID. No long rebuilds, no duplication of the heavy setup work.
For large teams, store the branch → snapshot mapping in metadata (snapshot.set_metadata
) so orchestration code can discover and reuse the right snapshot automatically.
Next steps
- Combine
Snapshot.build
withSnapshot.tag("vibe-app:branch-name")
to label the latest artifact. - Use the returned snapshot IDs to
instances.start
new replicas or pipe them into a deployment pipeline. - Add branch-specific verification steps (unit tests, smoke checks) as final callables so cached layers only graduate when they pass.
Infinibranch snapshots give you Docker-like ergonomics across any Morph Cloud workflow: define layers once, share them everywhere, and let Snapshot.build
keep the cache hot.