Blog
Should Every Tenant Get Its Own VM?
Choose between per-tenant containers, VMs, and hybrid setups with a risk-based decision framework and the hardening steps that make each option defensible.
Summary
Multi-tenant hosting forces you to choose how far tenants can reach into one another. Containers use Linux namespaces and cgroups to isolate processes and resources, but they share the host kernel. Virtual machines add a hardware-level boundary, at the cost of speed and operational load. A hybrid approach—containers inside VMs—can give you both, but it doubles the surface you must patch. This article walks you through a risk-based decision, a side-by-side comparison, and the Docker hardening steps that matter even inside a VM. By the end, you'll know which isolation model fits your tenants and what to configure before launch.
Your multi-tenant app is almost ready. You've got a Docker Compose file that spins up a stack per customer, and it's fast. Then a friend who runs a hosting company asks, 'Are you giving each tenant its own VM?' You freeze. You didn't plan for that question. This article gives you a way to answer it today, without a security team. You do this alone, so the decision needs to be simple enough to defend at 2 a.m.
Stop trying to find the 'best' model. Start by writing down what happens if a tenant's code takes over your host. Define the blast radius before you pick any tool. That exercise will tell you more than any benchmark ever will.
The Kernel Is the Roommate You Can't Evict
Containers are efficient because they share the host kernel. That sharing is the whole trick, and the whole risk. Linux namespaces give each container its own view of processes, networking, and filesystem. Control groups (cgroups) let you cap CPU, memory, and disk I/O so one tenant cannot starve the others. But neither creates a hardware wall.
Think of a container as a process with a really good fake ID. It believes it is on its own machine. The kernel, however, is one copy of Linux running on your host. If a tenant exploits a kernel vulnerability, namespaces become metadata and nothing more. An attacker who can call kernel functions can reach other namespaces on the same kernel. That is the container escape you keep hearing about.
Say you host a small B2B tool with one container per client. A client installs a sketchy plugin with a remote code execution bug. With default Docker settings, that process is running as root inside the container. Root in a container is still UID 0, and the kernel does not distinguish that UID from host root unless you explicitly map users. The attacker can attempt to break out, and the shared kernel is their target.
The failure does not need to be dramatic. A single tenant leaking memory can push the host into swap, slowing every other tenant. Without cgroup limits, one misbehaving loop is an availability attack. With them, it is a blocked process and an alert.
Does this mean containers are unsafe? No. It means you must treat the kernel as a shared trust zone. Before you choose, write a one-paragraph risk statement: 'If a tenant's container is compromised, the attacker can access: [list]. The business cost would be: [amount or impact].' If that paragraph scares you, you are not paranoid. You are honest.
For a deeper look at the isolation spectrum, from shared containers to fully separate stacks, see our guide on designing a multi-tenant Docker architecture.
Three Ways to Slice It (Pick One Before You Deploy)
There are really three architectures for multi-tenant isolation. Every 'best practice' is a combination of these.
| Approach | Isolation barrier | Best when | Hardest caveat |
|---|---|---|---|
| Per-tenant containers | Kernel namespaces + cgroups | Many small tenants, low risk per tenant, need density | One kernel exploit can break every tenant on that host |
| One VM per tenant | Hypervisor/hardware virtualization | Regulated data, hostile tenants, high value per tenant | Heavier, slower to provision, you patch an OS per tenant |
| Containers inside VMs | VM boundary around containerized workloads | Density plus a hard shell between groups | Costs and operational overhead nearly double |
Per-tenant containers. This is the default for most SaaS founders. Each tenant gets its own container or small Compose stack. Provisioning is instant, images are small, CI/CD is straightforward. Resource limits keep noisy neighbors from eating the server. The trade-off is the shared kernel. If you can keep workloads non-privileged and patch the host regularly, this is often the right first move.
Do not put two tenants in the same container. That is a shared kernel plus a shared runtime plus a shared filesystem. If one tenant uploads a file that creates a process, the other tenant is already in the same process table. A container is your unit of isolation; make it one tenant per container.
What about the database? If every tenant connects to one MongoDB or PostgreSQL instance with the same credentials, you have already added a huge shared component. Give each tenant separate credentials, and ideally a separate database or schema. Containers isolate the app; the database is often the first leak an attacker will test.
One VM per tenant. Give each tenant a full virtual machine. The hypervisor adds a hardware-level boundary, which is exactly what a kernel exploit must cross to reach the host. This matters for regulated environments or when tenants are untrusted. The cost is density and time. You now manage a fleet of operating systems, not just containers. Each VM needs updates, security agents, and monitoring. For a solo founder, that's real work.
Patterns that work at this tier: use infrastructure-as-code to create a VM from the same base image, bake updates into new images instead of patching live systems, and terminate workloads you don't recognize. Keep the VM's management port closed to the internet.
Containers inside VMs. This hybrid rarely gets discussed in beginner tutorials. You put a small VM around each tenant (or small group of tenants), then run containers inside that VM. The VM is a blast-radius container; the containers are just deployable units. This gives you the hard edge of virtualization and the reproducibility of images. It costs more, because you pay for virtualization overhead and container flexibility, but it can be the sanest long-term model when you cannot fully trust tenants.
One common micro-example: a tenant runs a Node API and a background worker. Instead of one huge container with both processes, use one VM, then two containers with different resource limits, a shared network, and no direct internet exposure for the worker. The VM provides the hard edge; the containers provide structure.
Which one should you choose? The table is your shortlist. The next sections make the decision concrete.
If You Choose Containers, Do These Six Things or Don't Bother
Per-tenant containers are fine if you treat every container as a potential attacker. That starts with configuration, not wishful thinking.
0. Cap resources before you trust anyone. Cgroups are a fairness mechanism and an availability defense. Set --memory and --cpus per container. A tenant that leaks memory should hit its own limit, not your server's. This is not a security boundary, but a noisy neighbor is an attack without a single line of code. A practical start: --memory 512m --cpus 0.5. For a worker process, start lower and scale up.
1. Run as a non-root user. Never let the container process use UID 0 unless you absolutely need it. Set a user in the Dockerfile and pass --user as an extra guard. An exploit running as an unprivileged user has far fewer paths to the kernel. In your Dockerfile, create a user: RUN useradd -u 10001 app and USER app. Do not skip this to save time.
2. Drop every capability you do not need. Linux capabilities split root's power into small pieces. Most web apps need almost none. Start with --cap-drop=ALL and add back only what you know you need. A container without CAP_SYS_ADMIN is much harder to use for namespace tricks. If your app tries to bind a privileged port, run it on a high port and put a proxy in front instead of granting NET_BIND_SERVICE.
3. Make the filesystem read-only. Your app should not write to its own container layer. Mount a tmpfs for state. An attacker who cannot write to disk has a much harder time planting persistence. A compromised PHP app trying to write a webshell will fail when the root filesystem is read-only. You can mount a named volume for a writable directory your app genuinely needs.
4. Apply seccomp and AppArmor or SELinux. These send risky syscalls to the discard pile. Docker ships a default seccomp profile; use it. Add an AppArmor profile for another layer. You do not need to master every syscall. You need to deny what a normal web worker never requires. Never run with --privileged. That flag disables almost every defense you just set up.
5. Segment the network. Do not give every container a route to every other container. Default deny, then open only the ports you need. A compromised database container should not be able to scan your admin panel. If tenants are in separate networks, a breach in one network cannot spread laterally.
A practical start:
docker run --user 10001 --cap-drop=ALL --security-opt no-new-privileges --read-only --tmpfs /tmp:rw,size=64M --security-opt seccomp=default.json --memory 512m --cpus 0.5 myimage
Put the same flags into a Compose file and apply them to every tenant. This is not complete, but it is a far stronger default than what docker run gives you out of the box.
For a deeper walkthrough, use our step-by-step hardening guide for Docker containers in multi-tenant hosting.
Docker's Enhanced Container Isolation Is the Exception You Should Know About
If you run inside a managed Docker environment, look for Docker's Enhanced Container Isolation (ECI). It uses user namespace isolation and a secure container runtime under the hood. Root inside a container maps to an unprivileged user on the host, so even a container running as root does not get host root privileges. It also blocks dangerous capabilities and syscalls by default. This is not something you can recreate with a few flags on vanilla Docker. If your platform supports it, turn it on. It does not remove the need for non-root users and resource limits, but it changes the risk math.
You can approximate part of this with user namespace remapping (userns-remap) in the Docker daemon. That is not as complete as a secure runtime, but it is better than nothing. If you use it, verify the UID mapping works before you trust it.
The VM Fallacy: Moving to Virtual Machines Is Not Hardening
Here is the contrarian part, and it is the part most people skip. If you move to one VM per tenant and then deploy your normal containers inside it, you have not removed your container security problem. You have added a wide cage. The container escape still works; the attacker just lands in the VM instead of on the host. That is a real improvement, but you still need the six steps.
The other trap is assuming the VM itself is safe. A default image with a weak SSH password, unpatched base packages, or an open management port is a gift. The hypervisor boundary only matters if the guest is hardened and updated. Otherwise, your 'secure VM' is a faster path to compromise because you feel safe and stop checking.
What a VM gets you is reducible blast radius. One tenant's disaster stays in one VM. What it costs you is your time. You become the sysadmin for as many operating systems as you have tenants. If you are a solo founder shipping a product, ask whether you have the hours to patch and monitor a fleet. If yes, VM per tenant can be the right call. If no, containers with strong hardening might be more honest.
Also remember your hypervisor host is a critical target. A compromised hypervisor can see all guests. Patch the host, not just the guests. The VM does not excuse you from host patching; it raises the stakes for missing it.
A caveat on the hybrid: do not assume containers inside a VM gives you 'two layers of security' for free. The VM adds a boundary; the container still needs non-root, capabilities, and seccomp. Otherwise the first layer is only as strong as the weakest container.
Four Questions That Settle the Debate in Ten Minutes
Do not optimize in the abstract. Ask yourself these four questions in order. Write the answers down.
1. What does my tenant have access to? If a tenant can only reach their own web app and database, per-tenant containers with strict network rules are defensible. If a tenant's data is regulated or financially sensitive, move toward VMs.
2. How much would one tenant's compromise cost me? Add up lost customers, legal exposure, and trust. If the number is bigger than the cost of running VMs, spend the money. If not, containers are a rational choice.
3. How many tenants do I have and how much do they pay? Many small subscribers: container density matters. A handful of large accounts: give each a VM and bill accordingly. Tenants that pay you less than a coffee should not each require an OS to manage.
4. Can I patch things on a schedule? Containers share one host kernel, so patching the host protects everyone. VMs multiply your patch targets. If you know you will skip updates, pick the architecture with fewer moving parts and harder defaults.
Your answers will cluster. Two or more VM-focused answers means you should not be defaulting to per-tenant containers. Three or more container-focused answers means VMs are premature. One counterintuitive result: a low-revenue tenant with access to sensitive data still needs the VM, because the regulatory cost has nothing to do with how much they pay.
Ship the Least You Can Trust, Then Earn More Isolation
Your first architecture does not have to be your final one. Start with the tightest setup you can actually maintain, then add isolation as your tenant base justifies it. For most solo operators, that means per-tenant containers with non-root, capped capabilities, read-only filesystems, seccomp, and network segmentation. For regulated or high-value tenants, jump straight to one VM per tenant, with containers only as a packaging layer inside.
Whatever you choose, write down the decision and revisit it quarterly. When you get your first 'should we move this tenant to a VM?' question, you will have an answer, and you will have the checklist to back it up. That is what isolation actually means: a trade-off you manage, not a technology you buy.
Before launch, run through our practical Docker isolation security checklist—it turns these decisions into a list you can verify before you show a page to a customer.

