๐ Chapter 03 โ Virtualization
VMs and containers. When to use which, why, and the mental model that makes the choice obvious.
Learning Objectives
- Explain the difference between a VM and a container
- Choose the right tool for a given workload
- Understand the tradeoffs (performance, isolation, complexity)
- Set up both on a TrueNAS-based home lab
Introduction
The home lab needs compute. You're running services (Immich, Jellyfin, the reverse proxy, the monitoring stack), testing things, learning. The two ways to run those workloads on a single physical machine are virtual machines (VMs) and containers.
Both have their place. Knowing which is which is the difference between an efficient home lab and a confusing one.
The mental model
A VM is a complete computer, emulated in software. It has its own CPU (virtual), its own RAM (allocated from the host), its own disk (a file on the host), its own network interface. To the VM, it's a real computer. It runs an operating system (Linux, Windows, FreeBSD, anything). The OS in the VM doesn't know it's virtual.
A container is a process (or a group of processes) running on the host, isolated from other processes. The container shares the host's kernel. It has its own filesystem (a "container image"), its own environment variables, its own network namespace. To the process in the container, it looks like a real environment. But there's no separate OS; the container is using the host's kernel.
Visually:
Both run "on the same hardware." The differences are in the abstraction layer.
When to use a container
Use a container when:
- The workload is a single application (a web server, a database, a self-hosted service like Immich).
- The workload doesn't need a different OS than the host.
- You want fast startup (containers start in seconds, VMs in minutes).
- You want low overhead (containers use less RAM and CPU than VMs).
- You want easy replication (a container is a config file + an image; reproduce anywhere).
Most self-hosted services are designed to run in containers. Immich, Jellyfin, Vaultwarden, Paperless, Nextcloud, Home Assistant, Grafana, Uptime Kuma โ all of them are typically deployed as containers.
When to use a VM
Use a VM when:
- The workload needs a different OS (Windows in a Linux host, or vice versa).
- The workload needs kernel-level access (e.g., custom kernel modules, raw hardware access).
- The workload needs strong isolation (you don't trust the software, or the consequences of escape are severe).
- You're running a full application stack that expects its own OS (a legacy app, a development environment that mimics production).
- You're learning OS concepts (run different distros, practice system administration).
VMs are the right answer for: a Windows VM for a Windows-only app, a Linux dev VM that mimics a production server, a security-sensitive workload, a learning environment.
The performance tradeoff
Containers are more efficient. They share the host kernel, so there's no hypervisor overhead. A container uses 10-50 MB of RAM for the runtime; a VM uses 500 MB-2 GB just for the OS.
VMs are more isolated. The hypervisor enforces a hard boundary between the VM and the host. A container escape (rare but possible) gives the attacker access to the host; a VM escape gives the attacker access to the hypervisor (much harder).
For most home lab use cases, the container efficiency wins. Use containers by default; reach for VMs when the isolation requirement is real.
Container runtimes
The most common container runtime is Docker. Other options: Podman, containerd, LXC. The conversation was explicit: Docker is the right default for the home lab. The ecosystem is huge, the documentation is everywhere, and the apps you want to run are designed for it.
Docker Compose is the right way to manage multi-container apps. A single docker-compose.yml file describes the services, networks, and volumes. One command (docker compose up -d) starts everything.
TrueNAS and virtualization
TrueNAS Scale (the Linux-based version) supports both containers and VMs natively. The setup:
- Apps (formerly "Plugins"): a curated list of self-hosted services that install with one click. These are containers, pre-configured for TrueNAS.
- Custom Apps: install any container from any registry using a custom docker-compose.yml or Helm chart.
- Virtualization: create VMs from the TrueNAS UI. Pick an ISO, configure CPU/RAM/disk, boot the VM.
For TK's build, the apps approach covers most needs. The VMs are for the things apps can't do.
The architecture pattern
For a home lab, the typical architecture is:
Apps run in containers managed by TrueNAS. Lab VMs run in KVM (the Linux hypervisor), also managed by TrueNAS. The two are isolated from each other but share the underlying TrueNAS host.
The "apps vs custom compose" question
TrueNAS has a curated "Apps" catalog. For most apps (Immich, Jellyfin, etc.), the catalog has a pre-configured install. This is the easy way.
For apps not in the catalog, or for apps that need custom configuration, you can use Docker Compose directly. Create a docker-compose.yml file in tank/Lab/docker/, then deploy it.
The conversation's recommendation: use the Apps catalog for things it covers; use Docker Compose for the rest. Don't fight the catalog when the catalog has what you need.
Storage for containers and VMs
From Chapter 01: the Lab dataset is for lab work. Both containers and VMs should use the Lab dataset for their storage.
For containers, that means:
- Docker volumes (named volumes managed by Docker) are stored in
tank/Lab/docker/volumes/by default. - Or, mount a host path (e.g.,
tank/Lab/<service>/data) into the container. The container sees it as/datainside.
For VMs, that means:
- The VM's virtual disk is a file (e.g.,
tank/Lab/vms/dev-vm/disk1.qcow2). - Or, the VM's virtual disk is a ZFS zvol (a block device in ZFS). Zvols are slightly faster but more complex.
For most use cases, host paths for containers and qcow2 files for VMs are sufficient. Zvols are an advanced topic.
The "why not just use the host" question
For very simple setups, you can install an app directly on the host (no container, no VM). Examples: a web server with apt install nginx, a Python app running under systemd.
The reason to use a container instead: isolation and reproducibility. The container packages the app, its dependencies, and its config. You can move the container to another host and it runs the same. You can have multiple versions of the same app on the same host. The container is sandboxed from the host's filesystem.
The reason to use a VM instead: full OS isolation. The VM is a complete computer; it can run anything that runs on that OS. The VM is fully isolated from the host.
For a small home lab with 2-3 services, the host-direct approach is fine. For 5+ services, or for anything that needs to be reproducible or isolated, use containers or VMs.
Common mistakes
- Running too many containers. The temptation is to install every self-hosted service that looks interesting. Resist. Each container uses RAM, CPU, and disk. For a 32 GB NAS, 10-15 small containers is reasonable; 50 is not.
- Running too many VMs. Each VM needs dedicated RAM. A 32 GB NAS can comfortably run 2-3 VMs (each with 4-8 GB). More than that, and you're swapping.
- Mixing lab and family containers. Put family services on the Apps dataset. Put lab containers on the Lab dataset. Don't cross the streams.
- Not backing up container data. The container image is reproducible (from a Dockerfile). The container's data (the database, the uploaded files) is not. Back up the data, not the image.
Engineering Note
Containers are the default. VMs are the exception. The vast majority of home lab workloads are best served by containers. The container ecosystem is mature, the documentation is everywhere, the apps you want to run are designed for it. Reach for VMs only when the workload really needs them โ a different OS, kernel-level access, or strong isolation. The default is the efficient one.
Summary
VMs are emulated computers. Containers are isolated processes sharing the host kernel. Use containers for most home lab workloads (self-hosted services, development, testing). Use VMs for different OSes, kernel access, or strong isolation. TrueNAS supports both natively. Use the Apps catalog for the easy stuff; use Docker Compose for the rest. Mount Lab dataset paths for storage. Don't over-provision; a 32 GB NAS can run 10-15 small containers and 2-3 small VMs.
Checklist
- โฌ Decide which workload is a container vs a VM (use the decision rules above)
- โฌ Use the TrueNAS Apps catalog for the apps it covers
- โฌ For custom apps, set up a
docker-compose.ymlintank/Lab/docker/ - โฌ Mount Lab dataset paths into containers, not the family dataset paths
- โฌ Monitor resource usage:
docker statsfor containers, the TrueNAS dashboard for VMs
Looking Ahead
Chapter 04 is the deep dive on Docker: Docker Compose, container orchestration for the home, the patterns that make a multi-container app actually maintainable. The chapter that turns "I have a container running" into "I have a container system I can manage."