The Evolution of Isolation
For decades, isolating applications on Linux meant spinning up full Virtual Machines using hypervisors like KVM or VMware. If you needed to isolate a simple Nginx web server, you had to allocate a massive 20GB virtual hard drive, dedicate 2GB of physical RAM, and run an entire secondary Linux kernel inside the VM. This architectural overhead makes running hundreds of isolated applications on a single physical server impossible.
While Docker popularized application-level containerization (bundling a single binary and its dependencies), it is not designed to behave like a full server. If you want a container that acts exactly like an Ubuntu VM—where you can SSH into it, run systemd, install packages via apt, and run multiple background daemons—Docker is the wrong tool.
The correct architectural tool is LXC (Linux Containers). LXC utilizes the host’s Linux kernel but uses Namespaces and Control Groups (cgroups) to mathematically slice the operating system into completely independent “System Containers.” An LXC container boots up in milliseconds, consumes almost zero idle RAM, and feels exactly like a dedicated bare-metal server to the administrator. Most importantly, modern LXC allows for Unprivileged Containers, meaning the root user inside the container has absolutely zero privileges on the physical host, mathematically neutralizing container-escape vulnerabilities.
Step 1: The Unprivileged Architecture (User Namespaces)
The security of an unprivileged container relies entirely on User Namespaces. This is a mathematical trick performed by the Linux kernel.
When you log into an unprivileged container as root (UID 0) and look at a file you created, it shows root as the owner. However, on the physical host operating system, the kernel has mathematically mapped that UID 0 to a massive, harmless, non-existent user ID (e.g., UID 100000).
If a hacker compromises the Nginx server inside the container and manages to “break out” onto the host filesystem, the host kernel sees them as UID 100000. They have zero privileges. They cannot even read /etc/passwd. They are mathematically impotent.
Step 2: Installing and Configuring LXD/LXC
On modern Ubuntu, the easiest way to manage LXC is through the LXD daemon, which provides a brilliant, RESTful orchestration layer over the raw LXC binaries.
LXD is usually pre-installed on Ubuntu Server via Snap, but if it is missing, install it:
sudo snap install lxd
Before creating a container, you must initialize the LXD storage pool and networking bridge. This defines where the container files will live (usually a ZFS or Btrfs loop device) and how they will reach the internet.
sudo lxd init
The initialization wizard will ask you several questions. For a standard enterprise deployment, accept the default ZFS storage backend (it provides instant, zero-copy snapshots) and accept the default lxdbr0 NAT network bridge.
Step 3: Provisioning an Unprivileged Container
LXD pulls pristine, pre-built OS images directly from Canonical’s image servers. To launch a brand new Ubuntu 22.04 container named web-node-01, execute the launch command:
lxc launch ubuntu:22.04 web-node-01
The exact millisecond you press Enter, LXD downloads the minimal root filesystem, creates the ZFS dataset, configures the network namespace, and boots the container. The entire process takes approximately 3 seconds.
By default, LXD creates unprivileged containers. The root user inside web-node-01 is safely mapped to the high UID range on the host.
Step 4: Interacting with the Jail
You do not need to configure SSH to access the container (though you can). Because LXD controls the kernel namespaces, you can forcefully inject a bash shell directly into the running container from the host.
lxc exec web-node-01 -- /bin/bash
Your prompt changes to root@web-node-01:~#. You are now inside the container.
Run ps aux. You will see a completely isolated process tree starting at PID 1 (systemd). Run ifconfig. You will see a dedicated eth0 interface with an IP address leased from the lxdbr0 bridge (e.g., 10.150.5.42).
You can run apt update, install Nginx, and host a website. The container genuinely believes it is a standalone physical server.
Step 5: Enforcing Mathematical Resource Limits (cgroups)
Because containers share the host kernel, a rogue container can theoretically consume 100% of the host’s CPU, starving all other containers. You must enforce Control Group (cgroup) limits.
Unlike Docker, which requires you to pass limits during the initial run command, LXD allows you to hot-swap hardware limits on live, running containers without rebooting them.
To mathematically restrict web-node-01 to a maximum of 2 CPU cores and 512MB of RAM, execute the config commands from the host:
lxc config set web-node-01 limits.cpu 2
lxc config set web-node-01 limits.memory 512MB
The exact millisecond you execute these commands, the host kernel intercepts the container’s cgroup. If you run htop inside the container, it will instantly recalculate and show exactly 2 CPUs and 512MB of total physical RAM. If the Nginx server attempts to request 513MB, the kernel will trigger an isolated OOM (Out of Memory) kill exclusively inside the container, protecting the host and all neighboring containers from disruption.
Conclusion
Relying on full hardware virtualization for every isolated application creates massive architectural overhead and wastes terabytes of expensive RAM. By deploying LXC/LXD System Containers, Ubuntu engineers achieve the operational flexibility of full Virtual Machines with the millisecond boot times and zero-overhead performance of bare-metal execution. The ability to leverage User Namespaces for unprivileged, mathematically secure execution, combined with hot-swappable cgroup resource throttling, makes LXC the ultimate architecture for high-density, multi-tenant enterprise hosting.