PandaStack vs Depot: Build Acceleration Is Not a Sandbox API
Decision first, because a page titled "X vs Y" is read by people who want one rather than an essay. If your problem is that your Docker builds take too long and your GitHub Actions cache has quietly become a liability, buy Depot. A remote builder with a real persistent layer cache, plus native multi-architecture builds that do not involve one CPU emulating another, is the correct shape of tool for that problem. A sandbox API is not. Close the tab with my blessing.
I'm Ajay. I build PandaStack, which runs code in Firecracker microVMs behind an API. Depot is a build-acceleration company: as its public documentation reads at the time of writing, the products are remote container builds on managed builders with a persistent cache attached, managed GitHub Actions runners, and cache acceleration for build tools that are not Docker.
The post still earns eleven minutes, and not for the feature grid at the bottom. Depot and PandaStack are the same bet placed on two different tables: that the dominant cost in modern compute is not doing the work, it is arriving at a state where the work can start. Depot attacks that with a cache that persists; I attack it with a machine you resume. Those mechanisms have genuinely different powers, and the middle third here is about which is the right abstraction for which problem.
What each one actually is
Depot: the build, and the cache behind it
As documented publicly at the time of writing, the core product is a drop-in replacement for `docker build` and `docker buildx`: you change the command, and the build executes on a managed remote builder with a persistent cache attached. Multi-architecture builds run on builders of the matching architecture rather than under emulation. Read their docs for the current shape — I am describing a category, not quoting a changelog.
The architectural insight is subtler than "their machines are faster", and it deserves respect. BuildKit already has a good cache: content-addressed, aware of your Dockerfile's dependency graph, happy to skip a step whose inputs it has seen. The problem was never the cache's design — it was what the cache is attached to. In a stock CI job it is attached to nothing durable, so the pipeline tars a cache directory at the end, uploads it to a blob store, downloads it next job, and untars it before building can start. Attach that cache to real persistent disk on a builder that stays and all of it stops happening. Most of the win is not compute; it is the deletion of a save-and-restore dance.
That dance deserves a name, because a great many pipelines still perform it nightly. A cache restored, used and re-uploaded on every single job is a cache in roughly the way a photocopier is a filing cabinet: the documents are all technically in there, you simply have to reproduce every one of them each time you open the drawer. And then there is the Dockerfile with `COPY . .` on line four, less a build instruction than a cache-invalidation weapon — every layer beneath it dies when someone edits a README.
How this differs from the Blacksmith comparison
I wrote a nearly identical post about Blacksmith (PandaStack vs Blacksmith: Mostly, Buy Blacksmith), and the pair is instructive, because "makes CI faster" is a market rather than a category. Blacksmith's product is the runner: the machine your job lands on, chosen by a label in your workflow file, with GitHub's Actions service still owning the control plane above it. Depot's distinctive product is the build and the cache behind it: you keep the runner you have, and the expensive step inside the job relocates to a builder that remembers things. One sells a better place for the whole job to sit; the other a better way to perform one step from wherever it already sits.
Depot sells runners too, so the lines overlap at the edges. The useful question is which asset is hard to replace, and for Depot it is the cache rather than the compute — one that stays warm across jobs, is scoped per project, and is still correct when two branches disagree about a dependency. That is also why comparing a sandbox API against Depot beats comparing one against a runner vendor: a runner and a sandbox differ at the interface, where a cache and a snapshot differ at the mechanism.
PandaStack: Sandbox.create() and a microVM
PandaStack has no builders, no `docker build` wrapper and no cache product. It has `Sandbox.create()`. You call it from Python, TypeScript or raw HTTP and a Firecracker microVM exists — Ubuntu 24.04, guest kernel 5.10, its own kernel, its own network namespace with a veth pair and tap device from 16,384 pre-allocated /30 subnets per host agent. Your program runs commands in it, reads files out, snapshots it, forks it, deletes it.
Let me be blunt about the gap, because it is a gap and not a positioning subtlety. If you point me at a slow `docker build` in CI, I have nothing for you: no BuildKit integration, no remote builder to target with `--builder`, no persistent layer cache as a product. `docker build` inside a sandbox takes exactly as long as the same build on any other cold machine with a local disk, because that is what a fresh microVM is. Anyone implying otherwise is selling you a virtual machine for a problem that wants a cache.
The shared bet, and why it is the interesting part
Strip both products to the premise and they say the same sentence: producing a warm environment is the expensive thing, so the mechanism that produces one cheaply is the product. Every company here answers three costs — the machine (hardware, a booted kernel, networking), the state (dependencies, caches, built artefacts, a seeded database, a running process), and the proof of cleanliness (nothing from the previous tenant is still in there).
Depot attacks the state cost with a persistent cache attached to a builder that does not evaporate between jobs. Qualitatively, because that is all I will say about someone else's internals: keep the expensive intermediate results on real disk, next to the thing that will need them, and stop shipping them across a network twice per job.
PandaStack attacks it with snapshot restore, and I can put numbers on this one because they are mine. There is no warm pool of idle VMs — not an optimisation we skipped but a deliberate choice, because a warm pool is a monthly bill for machines nobody is using. Every create restores a baked Firecracker snapshot: allocate a pre-built network namespace slot, patch the tap MAC, reflink the rootfs, fork and exec Firecracker, load the snapshot, resume, probe TCP :22. That is p50 179 ms and p99 203 ms, the snapshot load itself 49 to 80 ms of it. Only the first-ever spawn of a template pays a cold boot of about 3 seconds, and it bakes the snapshot so nothing after it does.
A layer cache and a snapshot are not the same tool in different hats
A layer cache is content-addressed and compositional. Each step is keyed by its inputs, so it can serve a step to a build it has never seen: a different branch, a different Dockerfile, a different project that runs the same `apt-get install` on the same base image. It composes — six layers from one lineage, the seventh from another — and degrades gracefully in the middle, because the unit of reuse is a step rather than a machine. Snapshot restore can do none of that: a snapshot is one machine's state at one instant, and no key you can compute makes it answer a question about a different build.
A snapshot is a whole machine at an instant, and the instant is the point. It restores processes already running, sockets already bound and listening, a page cache already populated, a JIT already warm, a runtime that has already imported its dependency graph. A layer cache can do none of that, because files are not a running state: hand a build its dependency directory and it still has to resolve, link, compile, start the process and wait for readiness. The cache saved the download, perhaps the compile — not the startup.
A cache is a bet that you can rebuild the state faster than you can keep it. A snapshot is a bet that you can keep it cheaply enough that you never rebuild it.
One asymmetry decides a lot of architectures: a snapshot can be forked and a cache cannot. Pay once to reach an expensive state, then run eight mutually exclusive attempts against it — a cache restores the ingredients eight times and you cook eight times, where a snapshot restores the finished machine eight times (Snapshots and Forks: Copy-on-Write for Running Machines has the mechanics). They also compose, which is why I say complementary rather than competing: the output of a cached build is an image, and the input to a baked snapshot is an image. Golden Images vs Snapshot Baking takes that further.
Where the categories genuinely collide
Three places. All three are real, and in two of them the answer is plausibly "use both".
Untrusted build inputs, which is a product question whether you like it or not
A build is not an inert transformation of text into an image. A `Dockerfile` has `RUN`, which executes arbitrary commands; `npm install` has `postinstall`, which executes arbitrary commands; most ecosystems have their equivalent. The moment you build a fork's pull request, or a customer's repository inside a hosted product, your builder is running a stranger's code with whatever credentials it holds. Scanning Package Postinstall Scripts in a microVM is the longer treatment.
So "where does the build execute" stops being a performance question. The honest threat model starts in your own repository, not with your vendor: `pull_request_target` hands fork-authored code a privileged context with your secrets in it, and that one mistake has compromised more pipelines than any builder architecture ever will — Fork-PR CI Without Getting Pwned: One microVM Per Job walks through it. Then ask what the boundary around the build is, and whether a cache shared across builds is shared across builds you trust differently.
A microVM per build is the answer when the requirement is not "reasonably isolated" but "demonstrably isolated to a named boundary": one kernel per build, one network namespace per build, a diagram an auditor accepts without a conversation. That is provability, not an accusation about anyone's engineering. It also costs you the cache — a fresh microVM has, by construction, no warm layer cache in it, and keeping one on a durable volume hands you the correctness problem you were avoiding.
Agent-driven builds, where the caller is a model
The second collision is newer. If the thing invoking `docker build` is a language model iterating on a Dockerfile it wrote thirty seconds ago, the economics invert. The build will fail often, which is fine — that is what iteration looks like. But every attempt is a stranger's code, the attempt rate is set by a model rather than by how often humans push, and what you want is less "fast" than "disposable and definitely gone afterwards".
A sandbox API is the right shape for that loop: create a microVM, let the agent try, read out what it produced, destroy the machine. Snapshot restore at p50 179 ms is what makes "a whole virtual machine per attempt" a sentence you can say in a request handler, and the blast radius of a model-generated `rm -rf` becomes a machine that was going to be deleted anyway. How to Build a Sandboxed AI Coding Agent (2026) is the build-out, and Sandboxing AI-Generated Terraform and Infrastructure-as-Code is the same argument where the generated artefact is infrastructure.
What it does not do is make the build faster: inside that microVM, `docker build` is still `docker build` on a cold local disk. If your agent loop is bottlenecked on the build rather than the model, you want a cache, and the interesting architecture points the agent's sandbox at a remote builder — the attempt is disposable, the cache is not.
Building the images that sandboxes run, which is where you use both
Here is the combined architecture, because it is the one a reader might actually deploy. PandaStack templates are built from Dockerfiles: hand the CLI a Dockerfile and a name, the build produces a root filesystem image, and the fleet cold-boots that image once and bakes it into a Firecracker snapshot — after which every create of the template is a restore. So a container build sits in the critical path of every template update, with ordinary container-build problems: a dependency layer that takes minutes, a multi-architecture matrix, and a cache that evaporates between runs.
The split follows from the cache-versus-snapshot distinction. A build-acceleration product owns Dockerfile-to-image, because that work is compositional and content-addressable; PandaStack owns image-to-running-machine, because that work is whole-machine. Concretely: CI builds the template image on a remote builder against a warm cache, both architectures natively; the image is exported as a root filesystem and published to object storage; each host agent syncs it and bakes it once, which is the one cold boot anybody pays for; from then on every sandbox of that template is a 179 ms restore. Guest sizing lands at build time too, since Firecracker cannot change vCPU count or RAM at restore. BuildKit vs Kaniko vs microVMs for Untrusted Image Builds is the deeper look at the image half.
One line on their side, twenty on mine
The interface difference is more convincing to look at than to read about. First the build-acceleration shape, as a workflow diff. The placeholder in it is deliberate, and you must not copy it.
# .github/workflows/image.yml
#
# The build-acceleration shape, conceptually. The point of this block is the
# SHAPE, not the strings: a remote-build product replaces the build command
# and leaves the rest of the job alone -- same runner, same checkout, same
# tags, same registry push.
name: image
on: [push]
jobs:
image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# BEFORE -- the build happens on the runner, against a cache that does
# not exist until you hand-roll one:
#
# docker buildx build --platform linux/amd64,linux/arm64 \
# --cache-from type=gha --cache-to type=gha,mode=max \
# -t ghcr.io/acme/api:$GITHUB_SHA --push .
#
# Those two cache flags ARE the save-and-restore dance: tar the cache,
# push it to a blob store, pull it back next job, untar it, then start
# building. Note also the arm64 half of that --platform list, which on
# a stock amd64 runner means one CPU emulating another.
# AFTER -- the remote-builder shape. DO NOT COPY THIS PLACEHOLDER.
# The real setup action, the real command name, the auth step and any
# project/token flags come from the vendor's current docs. Command
# surfaces are exactly the thing that moves between a post being
# written and you reading it.
- run: |
<vendor-remote-build-command> \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/acme/api:$GITHUB_SHA \
--push .
# Notice what is NOT in the AFTER block: no --cache-from, no --cache-to,
# no cache action, no tarball. The cache lives on the builder's own
# persistent disk. That absence is the entire architectural idea.
# Homework before you buy anything: open your Dockerfile and find the
# `COPY . .` sitting above the dependency install. Every layer beneath
# it dies when someone edits a README. A remote cache makes a
# badly-ordered Dockerfile fast. Reordering it makes the build fast AND
# cheap, and costs one commit.
And the sandbox shape for the neighbouring problem: run a build you do not trust, extract one artefact, destroy the machine.
import os
from pandastack import Sandbox
# The job: build an untrusted contributor's project inside a machine that is
# allowed to be destroyed, and get exactly one artefact out of it.
#
# What this is NOT: a faster build. Inside this microVM the build runs on a
# cold local disk with no shared layer cache, so it takes as long as the same
# build on any other cold machine. If build SPEED is your problem, this is the
# wrong tool and a remote builder with a persistent cache is the right one.
# This is for when the problem is WHOSE CODE IS RUNNING.
assert os.environ["PANDASTACK_API_KEY"], "set PANDASTACK_API_KEY"
# create() does not boot a VM and does not take one from a warm pool -- there
# is no warm pool. It restores a baked Firecracker snapshot: allocate a
# pre-built netns slot, patch the tap MAC, reflink the rootfs, fork/exec
# firecracker, POST /snapshot/load, resume, probe TCP :22.
# p50 179 ms, p99 203 ms, of which the snapshot load itself is 49-80 ms.
# Only the first-ever spawn of a template cold-boots (~3 s) and bakes the
# snapshot, so that nothing after it pays that cost.
sbx = Sandbox.create(
template="base", # Ubuntu 24.04 + mise, with Node 24 LTS / Python 3.12 / Go / Bun pre-warmed
ttl_seconds=1800, # enforced by a platform-side reaper, so a crashed
# orchestrator cannot leak a running VM at your expense
metadata={"repo": "acme/contrib", "pr": "4412", "trust": "untrusted"},
# cpu and memory_mb are accepted here and then corrected to the template's
# baked values. Firecracker cannot change guest RAM or vCPU count at
# snapshot restore, so sizing is a template-build decision, not a create
# argument -- and the correction is silent, which is worse than an error.
)
try:
sbx.exec(
"git clone --depth 1 https://github.com/acme/contrib /work",
timeout_seconds=120,
check=True,
)
# timeout_seconds is a CLIENT deadline. The server does not kill anything
# when it expires: your SDK call stops waiting, and the build keeps running
# and keeps costing. Put the real wall inside the guest.
r = sbx.exec(
"timeout 900 sh -c 'cd /work && npm ci && npm run build'",
timeout_seconds=960,
)
print("exit", r.exit_code)
print(r.stdout[-4000:])
# Note the absence of --ignore-scripts. On a shared builder I would use it,
# because a postinstall script there runs next to other people's builds and
# whatever credentials the builder holds. The reason I can leave it off here
# is that the blast radius is a VM with its own kernel that gets deleted in
# the `finally` below. The trade is explicit, not free: egress on this
# platform is OPEN by default -- a few targeted DROP rules exist for
# known-abuse protocols, but it is not a default-deny network, so "it
# cannot phone home" is something you build, not something you inherit.
if r.exit_code == 0:
# filesystem.read returns bytes. Copy the artefact out while the
# machine still exists -- nothing survives the delete.
artifact = sbx.filesystem.read("/work/dist/bundle.js")
with open("bundle.js", "wb") as f:
f.write(artifact)
print("pulled", len(artifact), "bytes")
finally:
# The only lifecycle discipline that survives contact with production:
# tear down on the success path AND the exception path. The verb on the
# Sandbox object is kill() (the client-level call is
# client.sandboxes.delete(id)); the TTL is the backstop, not the plan.
sbx.kill()
A word on fan-out, since this is where people reach for `fork()` and then file a bug. `fork()` clones the sandbox's disk with a reflink and then cold-boots the clone: it does not inherit guest memory, so the child gets its own kernel, its own PIDs and its own entropy pool, and what you inherit is the installed dependencies on disk, not the running build. Because it cold-boots, it is the roughly-3-second shape rather than the sub-second one. If you want N copies of a warm, mid-build machine, the verb is `fork_tree(count=N)`, which snapshots the parent once and restores children that inherit memory as well as disk — 400 to 750 ms each on the same host, 1.2 to 3.5 s across hosts, where the snapshot has to travel first. Both cap at 16 children per call.
The decision table
| Dimension | Depot (build acceleration) | PandaStack (microVM sandbox API) |
|---|---|---|
| What it accelerates | The container build step, by running it on a managed remote builder with a persistent cache attached | Nothing about a build. The appearance of a machine: every create is a snapshot restore, p50 179 ms, p99 203 ms |
| Interface | The build command. Swap `docker build` / `docker buildx` for the vendor's equivalent; the rest of the job is untouched | `Sandbox.create()` from Python, TypeScript or REST. No YAML, and no build command anywhere in the product |
| Unit of reuse | A build step, content-addressed by its inputs, composable across builds, branches and projects | A whole machine at an instant: running processes, bound sockets, warm page cache. Neither composable nor content-addressed |
| What it cannot reuse | A running state. A cache hands back files; the build still resolves, links, compiles and starts processes | A step. No key you can compute makes a snapshot answer a question about a different build |
| Where warm state lives | On the builder's own persistent disk, rather than tarred up and moved per job | A baked snapshot: memory file, VM state and rootfs, restored with `MAP_PRIVATE` memory CoW and XFS reflink disk CoW |
| Isolation boundary | Read their current isolation and cache-scoping docs. The answer is not mine to give | Firecracker microVM: own kernel, own netns, own veth pair and tap, from 16,384 pre-allocated /30 subnets per host |
| Idle cost | A builder-and-cache question — read their pricing | Storage, not reserved capacity. Hibernate snapshots and releases the host. $0.054 per vCPU-hour, $0.0162 per GiB-hour |
| Branch a warm state | No analogue: a cache gives every consumer the same ingredients, not a running machine | `fork()` clones disk then cold-boots (~3 s); `fork_tree(N)` restores N children from one snapshot with memory — 400-750 ms same host, 16 max |
| Honest ceiling | A build accelerator. If your problem is not a build, there is no command to swap | No BuildKit integration, no layer-cache product, no GPU, no nested virtualisation, guest kernel 5.10, RAM fixed at bake time |
When to buy which, including when to buy neither
Buy the build accelerator: the build is the bottleneck
Your image build is ten minutes, of which eight is a dependency layer that rebuilt because the cache came up cold again. You maintain an arm64 variant that takes multiples of the amd64 one, because it runs under emulation. Somewhere in a workflow file is a `cache-from` and `cache-to` incantation that works, that nobody understands, and that everybody is slightly afraid of. That is a cache problem with a cache answer: swap the build command, measure for a week, revert if it disappoints.
Buy neither: you are optimising the wrong thing
The row most comparison posts omit, which tells you something about comparison posts. If your pipeline is a few minutes on GitHub's hosted runners and the complaint is vibes rather than a measurement, open the job timings first. The answer is frequently free.
- A `COPY . .` above the dependency install, so every layer beneath it dies on a README edit. One commit, and the cache you already have starts working.
- A test suite running serially on a machine with idle cores, because nobody passed the parallel flag.
- An image rebuilt on every push to every branch, including the dozen nobody deploys. A path filter is cheaper than a vendor.
- A language setup step configured without its own caching, re-downloading a toolchain every run.
Each is cheaper than anything anyone can sell you, and each makes a vendor look better than it is if you buy first and fix second: you will credit your own commit's improvement to the invoice.
Buy the sandbox API: the problem is whose code is running, or who is calling
Two shapes qualify. The first is untrusted execution at request rate: a model emits Python, your product runs it, the result goes back into a conversation hundreds of times an hour, and no execution may see another tenant's data or the inside of your production network. There is no build in that story and no cache to warm. What matters is how quickly a machine appears inside a user-facing latency budget, and how little an idle one costs — which is why hibernate deletes the microVM and keeps only the snapshot.
The second is forking warm state, the capability with no cache-shaped equivalent. You have an environment that was expensive to reach — repository cloned, dependencies installed, database seeded, application built — and you want eight mutually exclusive attempts against that exact state, keeping whichever works: eight dependency-bump candidates, or eight patches a model generated for one failing test. Build acceleration is not a substitute for that at any price.
Buy both, which is correct more often than it sounds
If you run a hosted product that builds and then runs customer code, you have both problems, and buying both is not a diplomatic fudge — it is the architecture I would draw. Put the seam at the image: build it with a cache, serve it with a snapshot. The budgets are not competing either: build spend scales with how often your engineers push, sandbox spend with how much your customers use the feature that runs their code. Conflating them produces a consolidation project justified by a saving that was never a duplicate.
What PandaStack cannot do, so the table is not a sales document
I asked you to verify everything I said about Depot, so here is the list I would want handed to me about my own product before a purchase order.
- There is no build acceleration. No remote builder, no BuildKit integration, no persistent layer cache product.
- RAM and vCPU are fixed at template-bake time, because Firecracker cannot change them at restore. A `memory_mb` passed to create is silently corrected — worse than an error, because it is quiet.
- `fork()` clones the disk and cold-boots the clone, so it is the ~3 s shape; only `fork_tree(count)` branches from live memory and restores in 400-750 ms on the same host. Both cap at 16 children per call, and confusing the two is the commonest error with this API.
- Egress is open by default. A few targeted DROP rules exist for known-abuse protocols, but this is not a default-deny network; if your threat model needs one, you build it.
- `timeout_seconds` on exec is a client deadline, not a server-enforced kill. A runaway build needs `timeout` or `ulimit` inside the guest, with `ttl_seconds` as the backstop.
- No GPU in the guest and no nested virtualisation. The guest kernel is 5.10 and you do not choose it, so a build needing KVM inside the job is on the wrong substrate.
- Managed Postgres creates take 30 to 90 seconds, because the call blocks until the database is ready. Sub-second is a sandbox property, not a database one.
How I would decide, in one question
Ask what is slow, and what it is slow at. If the answer is "producing an artefact from source" — compiling, installing dependencies, assembling layers — the expensive work is compositional and content-addressable, and you want a cache attached to something that persists. Buy build acceleration: it is a command rather than an architecture, so the experiment is nearly free to reverse, and that reversibility is most of why it should be your first move.
If the answer is "getting to a machine where work can start, over and over, in response to something that is not a git push", then no cache helps, because there is no build. You want machines that appear in a couple of hundred milliseconds, cost nothing while idle, and can be branched from a warm state.
If the answer is both, buy both and put the seam at the image. Which is why this post spent its middle third on the difference between a key and an instant rather than on a feature grid: once you can say which of those two things your expensive work actually is, the vendor question answers itself.
Frequently asked questions
Can PandaStack make my docker build faster?
No, and I would rather say so here than in a sales call. There is no remote builder, no BuildKit integration and no persistent layer cache in the product. Run docker build inside a PandaStack sandbox and it runs on a freshly restored machine with a local disk and an empty cache, so it takes exactly as long as the same build on any other cold machine. The 179 ms create number describes how fast a machine appears, not how fast anything inside it compiles, and conflating those two is the easiest way to misread a microVM pitch. The nuance worth knowing: you can attach a durable volume and keep a build cache on it, which gets you a warm cache across runs on the same host. That works, and it is a smaller, more fragile version of what a build-acceleration product sells, with the cache-correctness problems now yours — scoping per project, invalidating properly, deciding what happens when two builds you trust differently want the same cache. If the bottleneck is the build, buy the thing designed for the build.
What is the real difference between a layer cache and a snapshot?
A cache restores files; a snapshot restores a machine. A layer cache is content-addressed and compositional: every step is keyed by its inputs, so it can serve a step to a build it has never seen, compose the first six layers of one lineage with the seventh of another, and invalidate only the steps below your change. Snapshot restore has none of that generality — a snapshot is one machine's state at one instant, and no key makes it answer a question about a different build. What it has instead is the running state: processes already running, sockets bound and listening, a page cache populated, a JIT warm, a runtime that has already imported its dependency graph. A cache cannot deliver that, because files are not a running state — hand a build its dependency directory and it still has to resolve, link, compile, start the process and wait for readiness. So: is the expensive thing obtaining files, or reaching a running state? Files means cache. Running state means snapshot. And one asymmetry decides many architectures — a snapshot can be forked into N copies of a finished machine, where a cache can only hand N consumers the same ingredients.
Is it safe to build untrusted pull requests on a shared remote builder?
Often yes, and the honest reason is that the builder is usually not the weak link. The two things that actually get people compromised by fork pull requests are pull_request_target, which runs workflow code in a privileged context with access to your secrets while checking out fork-authored code, and long-lived self-hosted runners that keep state between jobs so one job can poison the next. Both are configuration decisions in your own repository, not properties of who supplies the compute. Fix those first: use pull_request for anything touching fork code, do not expose secrets to fork-triggered jobs, and never point a persistent self-hosted runner at a public repository. After that, the builder-specific questions are what the isolation boundary around a build is and how the cache is scoped, because a cache shared across builds you trust differently is a channel between them. Read the vendor's current docs on both rather than my summary; I will not state another vendor's security properties as fact. Where I would push further is when the requirement becomes demonstrable isolation to a named boundary — one kernel per build, a diagram an auditor accepts. That is a provability argument, and it costs you the warm cache.
If I use a remote builder and PandaStack together, where exactly is the handoff?
The image. PandaStack templates are built from Dockerfiles: you hand the CLI a Dockerfile and a name, the build produces a root filesystem image, and the fleet cold-boots that image once and bakes it into a Firecracker snapshot. Every create of that template afterwards is a restore rather than a boot. So the handoff is clean: your build system owns everything up to the image, with its persistent cache and its native per-architecture builders; PandaStack owns everything after it, because turning an image into hundreds of running machines is whole-machine work. In practice: CI builds the template image on a remote builder against a warm cache, the image is exported as a root filesystem and published to object storage, host agents sync it and bake it once, and from then on each sandbox is a restore at p50 179 ms. The bake being per template rather than per sandbox is what makes this pleasant — a slow build sits in a release pipeline where ten minutes is merely annoying, not in anybody's request path.
Depot, Blacksmith, PandaStack — the one-line version of each, and the trap?
Depot's distinctive product is the build and the cache behind it: swap the build command and the expensive step relocates to a managed builder with a persistent cache attached, so the cache stops being something you tar up and restore every job. Blacksmith's product is the runner: swap a label in your workflow file and your jobs land on its machines, with GitHub's Actions service still owning the control plane above them. PandaStack is neither — Sandbox.create() and a Firecracker microVM, called from your own program, with no workflow file and no build acceleration in it. The first two both make CI faster, which is a market rather than a category, and the category difference is where the value sits: one sells a better place for the whole job to sit, the other a better way to perform one step from wherever it already sits. The trap is that all three turn up in the same searches about ephemeral compute, so it is easy to buy by row count on a feature grid. Ask instead what is slow and what dispatches it. Slow build, pushed by engineers: build acceleration. Slow job, pushed by engineers: a runner. A machine per user request or per model attempt: a sandbox API.
Keep reading
- PandaStack vs Blacksmith: mostly, buy Blacksmith — The sibling comparison, and the category contrast: Blacksmith sells the runner, Depot sells the build.
- BuildKit vs Kaniko vs microVM image builds — The image-production half on its own terms, including what each approach needs in the way of privilege.
- Golden images vs snapshot baking — Why an image and a snapshot are different artefacts, and which part of a pipeline each belongs in.
- Fork-PR CI without getting pwned — Why pull_request_target is the real hazard, and what a per-build kernel buys on top of fixing it.
- Postinstall scripts and registry scanning in microVMs — The delivery mechanism behind "my build just ran a stranger's code", and how to contain it.
Related posts
- PandaStack vs Namespace Cloud: Two Kinds of "Ephemeral"
Three different things are called "namespace" and only one of them is a company. Here is the disambiguation, and then the honest comparison: a managed-runner-and-cache product against a microVM sandbox API, including the two places the choice is genuinely real.
- Docker-in-Docker vs microVMs for CI builds
Your CI runners are containers and your jobs need to build images. Every answer to that trades privilege for convenience — here's the honest accounting of all four.
- Building Container Images for Untrusted Repos Without Privileged Docker
Every RUN line in a user's Dockerfile is a shell command on your builder, as root, at build time. The build is the untrusted workload — not the thing you build.
- Running Nix Builds Inside a Disposable VM
Nix's sandbox is a hermeticity fence, not a hypervisor — a hostile derivation still runs on your kernel. Here's how to put Nix inside a disposable VM without a cold /nix/store making every build miserable.
- Bazel Remote Execution Workers on Firecracker microVMs
A remote cache poisoned by a leaky worker is a supply-chain compromise with excellent uptime — every developer pulls it, nobody rebuilds it, and the digests all check out.
More in CI & ephemeral environments · See Ephemeral CI runners on PandaStack
49ms p50 cold start. Fork, snapshot, and scale to zero.