Self-Hosted Sandboxes: How to Pick Between Containers and MicroVMs
This guide is for developers who want to roll up their sleeves and build their own sandboxing infrastructure. While managed services offer…
Self-Hosted Sandboxes: How to Pick Between Containers and MicroVMs

Sand castle with colourful toys on a beach somewhere.
This guide is for developers who want to roll up their sleeves and build their own sandboxing infrastructure. While managed services offer convenience, they can get expensive fast, especially for experiments or prototypes.
This guide focuses on the DIY, self-hosted path as a powerful and cost-effective alternative. Specifically, we’ll dig into the two main options you have today: old-school containers (Docker, Podman) and the new hotness, microVMs.
We’ll walk through:
- When containers are enough
- When you must graduate to microVMs
- A simple path from “works on my machine” to “secure-ish in production”
Let’s dive in.
1. Containers vs. MicroVMs in Plain Language
There’s one key difference:
- Containers share the host kernel
- MicroVMs get their own kernel
Everything else is trade-offs.
Containers: The Roommate
Think of a container as a roommate in a shared apartment.
You each get:
- Your own room → your process tree and filesystem
- Your own locks → Linux namespaces and cgroups
But you share:
- The foundation, plumbing, and electrical system → the host OS kernel
That’s the mental model. You’re separated — but not completely.
- What containers are: A way to package and isolate your app using the host’s kernel
- Why they’re everywhere: They’re fast, lightweight, and the tooling is world-class (hello, Docker Hub)
In practice:
- Startup time is usually ~10–50ms
- Overhead is tiny
- The ecosystem is enormous
This is why containers are the default for most internal tools, web apps, and microservices.
MicroVMs: The Private Island
Now flip the model.
Instead of roommates in one apartment, imagine giving each tenant their own tiny private island:
- Each island gets its own power, water, and laws → its own kernel
- The boundary between islands is enforced by hardware, not just software tricks
That’s a microVM.
- What microVMs are: Tiny, stripped-down virtual machines
- What they use: Hardware virtualization to create a hard isolation boundary
- Why they exist: To safely run untrusted code on shared hardware
Modern examples:
- AWS Firecracker
libkrun-based systems like microsandbox
They:
- Boot in roughly ~125ms+
- Use a few MB of RAM per VM
- Offer real isolation between tenants
Here’s the trade-off in one table:

Table that compares containers to microVMs
The old story that “VMs are slow and bloated” is outdated. MicroVMs are quickly becoming the default for high-security, multi-tenant code execution.
2. The Real Question: How Much Do You Trust the Code?
When you’re self-hosting, the real decision filter is simple:
How much do you trust the code you’re running?
Everything flows from that.
When Containers Are the Right Choice
Use containers when:
- You control the code (your own apps, internal tools, CI runners)
- Your team is the only tenant
- Raw startup speed matters more than hard isolation
For these workloads, containers are ideal:
- They’re cheap
- They’re easy to deploy
- Every hosting provider and DevOps tool speaks “Docker”
But there’s a catch. Because containers share the host kernel, you’re exposed to:
- Kernel exploits (if someone escapes the container)
- Misconfiguration (the far more common way people wreck their security)
You can’t eliminate the kernel risk completely, but you can make containers much safer with a few non-negotiables.
Baseline Hardening for Containers
If you’re self-hosting containers, this is the minimum bar:
- Run as a non-root user
Don’t run your app as
rootinside the container - Use minimal base images
Prefer
distrolessoralpineto shrink your attack surface - Set tight resource limits
Use flags like
--memoryand--cpusso one container can't starve the host - Drop capabilities by default
Start with
--cap-drop=ALLAdd back only the capabilities your app actually needs - Use a read-only root filesystem
Run with
--read-onlywhen possible
Here’s what’s interesting:
The biggest danger usually isn’t a brand-new kernel 0-day. It’s:
- Running containers with
--privileged - Mounting the Docker socket into a container
- Exposing more of the host filesystem than you meant to
If that kind of misstep is unacceptable for your threat model, containers are no longer enough. You need stronger isolation.
When You Need MicroVMs Instead
You should seriously consider microVMs when:
- You’re running **untrusted code from strangers
- **Public code execution services
- Multi-tenant SaaS platforms
- Plugins and extensions that execute user logic
- You’re building AI agents that can run arbitrary code and you don’t fully trust what they’ll do
- A compromise of one tenant must not risk the host or other tenants
MicroVMs give each sandbox its own kernel. That’s the key upgrade.
If an attacker:
- Exploits a bug in the guest kernel
- Gains full control of the code inside the microVM
They’re still stuck inside that microVM. The host and other tenants are protected by a hardware-enforced boundary.
The trade-offs:
- More complexity: You need:
- Hardware virtualization (VT-x or AMD-V)
- A microVM stack like Firecracker or a
libkrun-based system such asmicrosandbox - More overhead than containers: Still lightweight, but not free
If you’re running untrusted workloads, this trade is usually worth it.
3. From Laptop to VPS: A Simple Path
Let’s get practical.
How do you go from:
“I have some code running in a Docker container on my laptop”
to:
“I have a self-hosted sandbox on a remote server”?
Here’s a simple three-step path.
Step 1: Prototype Locally with Containers
Don’t start with microVMs. Start with what you already know: containers.
Locally:
- Package your workload into a Docker/OCI container image
- Figure out:
- What dependencies it needs
- What files it needs to read/write
- What ports it needs to expose
- Iterate until you have a single, reliable
docker runcommand that: - Starts your workload
- Works every time
At this point you’ve:
- Defined your runtime environment
- Identified your dependencies
- Proven the basic sandboxed workflow Crucially, that image is an ordinary OCI/Docker image. You’ll reuse this same image whether you run it directly with Docker on a VPS (Path A) or let
microsandboxboot it inside a microVM (Path B).
Only then should you move to a server.
Step 2: Pick a VPS That Won’t Fight You
Next, you need a server.
You’re looking for a VPS (or bare-metal machine) with:
- Virtualization support (if you plan to use microVMs)
- Look for nested virtualization / VT-x / AMD-V
Providers like **OVH and [Hetzner](https://www.hetzner.com/cloud/)** are popular because:
- They’re relatively cheap
- They give you enough control to run containers and microVMs
Once you’ve picked a provider, spin up a small instance and you’re ready for deployment.
Step 3: Choose Your Deployment Path
On your new server, you now have two clear paths:
- Path A: Container-only quick win
- Path B: MicroVM-based secure path
Let’s walk through both.
4. Path A — The Container-Only Quick Win
This is the “get something working today” option.
On your VPS:
- SSH into the server
- Install Docker (or your container runtime of choice)
sudo apt update && sudo apt install docker.io
- Pull your image and run it using the same
docker runcommand you perfected locally—now with hardening flags
And you’re done.
You now have:
- A working sandbox
- Running in production
- With minimal friction
The downside:
- You’re still on the container isolation model
- You’re still sharing the host kernel
For trusted workloads, this is often enough. For untrusted workloads, it’s a good prototype — but not your final form.
Path A takes the image you built in Step 1 and runs it directly with Docker on your VPS. Path B keeps that same image but changes the isolation boundary: instead of a plain container, microsandbox runs it inside a microVM.
5. Path B — The MicroVM Secure Path (using microsandbox)
Now let’s talk about the more secure path.
We’ll use microsandbox as an example, because:
- It builds on top of
libkrun - It’s designed specifically for sandboxed code execution
- It aims to make microVMs feel as approachable as containers And we’ll assume you’ve already packaged your workload as a Docker/OCI image in Step 1 —
microsandboxconsumes that same standard image format and runs it inside a microVM instead of a plain container.
Here’s the high-level flow:
- Get a virtualization-ready server
- Install
microsandbox - Run the server securely
- Expose it over the network (carefully)
- Connect a client and run workloads
Let’s go step by step.
5.1 Provision a Virtualization-Ready Server
First, confirm your machine supports virtualization:
- Look for Intel VT-x or AMD-V
- If you’re using a VPS, make sure the provider supports nested virtualization
This is a hard requirement for microVMs. Without it, you can’t use Firecracker, libkrun, or tools built on top of them.
Providers like Hetzner and OVH often support this out of the box — just double-check their docs or ask support.
5.2 Install microsandbox
Once your server is ready, SSH in and install microsandbox:
curl -sSL https://get.microsandbox.dev | sh
This bootstraps the microsandbox tooling onto your machine.
5.3 Start the Server (Without Shooting Yourself in the Foot)
This is where many people make a quiet but serious mistake.
The microsandbox docs often show a development-friendly command:
msb server start --dev
This is great for local testing.
But here’s the catch:
--devrelaxes important security defaults- It’s not meant for production
For a real deployment:
- Do not use
--dev - Start the server without it
- Use configuration files and flags intended for production
The exact options will evolve over time, so follow the microsandbox docs. The principle stays the same: dev flags are for dev.
5.4 Configure Networking and Remote Access
By default, microsandbox plays it safe:
- The server listens only on
127.0.0.1 - Nothing is exposed to the outside world
That’s a good default. But on a VPS, you’ll often want to:
- Run the server on the host
- Connect to it from a different machine or app
So you need to do two things.
5.4.1 Server-Side: Expose the Port
First, bind the server to all network interfaces:
# Check the microsandbox docs for the latest flags
# This example uses port 7263 ("SAND" on a phone keypad)
msb server start --host 0.0.0.0 --port 7263
Next, open the port in your firewall. On many Linux servers using ufw:
sudo ufw allow 7263/tcp
If your provider uses security groups or a web UI for firewall rules, add an inbound rule for that port there too.
5.4.2 Client-Side: Point Your App at the Server
Now you need a client to talk to your microsandbox server.
For Python, you might use a PythonSandbox client that:
- Defaults to a local server
- Can be configured to talk to your remote VPS
Often you’ll configure the server address via an environment variable:
import asyncio
from microsandbox import PythonSandbox
# Example:
# export MCP_SERVER_URL="http://<your_vps_public_ip>:7263"
async def main():
# The client connects to the remote microsandbox server
async with PythonSandbox.create(name="my-remote-app") as sb:
exec = await sb.run("print('🚀 Secure execution from afar!')")
print(await exec.output())
asyncio.run(main())
The exact client API may differ, but the pattern is always:
- Run a server on your VPS
- Point a client at its URL
- Execute code inside microVM-backed sandboxes
5.5 Package and Launch Workloads
Here’s some good news:
microsandboxis OCI-compatible- You can often reuse your existing container images
On top of that, it gives you specialized sandbox types:
PythonSandboxNodeSandbox- And more via their client SDKs (Python, Node.js, Rust, etc.)
So you can launch other runtimes just as easily. For example, a Node.js workload:
import asyncio
from microsandbox import NodeSandbox
# Assumes MCP_SERVER_URL is set to your remote server
async def main():
async with NodeSandbox.create(name="my-node-app") as sb:
exec = await sb.run("console.log('Hello from a Node.js sandbox!');")
print(await exec.output())
asyncio.run(main())
The result:
- Each sandbox runs inside its own microVM
- A compromise inside one sandbox does not compromise the host
- You get strong isolation without building everything from scratch
6. Where to Go from Here
If you want to go even deeper, there’s a next level:
- Running raw Firecracker directly
- Wiring up your own:
- VM lifecycle management
- Networking
- Storage
- Image management
This is powerful — but it’s also a big engineering lift.
If you’re interested in the full Firecracker-from-scratch path, keep an eye out for deep-dive guides that walk through compiling Firecracker, setting up tap devices, configuring block devices, and managing thousands of microVMs efficiently.
For most builders, though, you don’t need that on day one.
7. Final Word: Start Simple, Then Level Up
Here’s the practical takeaway:
- Start with containers.
- They’re good enough for most early workloads.
- They’re fast, cheap, and everyone knows how to use them.
- Harden them properly.
- Non-root user, minimal images, tight limits, dropped capabilities, read-only filesystems.
- Upgrade to microVMs when your threat model changes.
- Anonymous users, untrusted code, sensitive data, higher visibility.
It’s a spectrum:
- On one end: Simple, container-only setups that get you shipping fast.
- On the other: Hardware-isolated microVM platforms designed for hostile workloads.
You don’t have to start on the secure end of the spectrum. But as your scale, risk, and exposure grow, you should move in that direction.
When that happens, migrating from a container-based prototype to a microVM-backed system (using tools like microsandbox) is a logical next step—not a total rewrite.
Other Resources
메타데이터
- post_id
- 1fa4803b7bdf
- slug
- self-hosted-sandboxes-how-to-pick-between-containers-and-microvms-1fa4803b7bdf
- url
- https://medium.com/@odafe41/self-hosted-sandboxes-how-to-pick-between-containers-and-microvms-1fa4803b7bdf
- canonical_url
- https://medium.com/@odafe41/self-hosted-sandboxes-how-to-pick-between-containers-and-microvms-1fa4803b7bdf
- author_url
- https://medium.com/@odafe41
- status
- ok
- fetched_at
- 2026-06-09 15:37:30