OS-level virtualization for running multiple isolated Linux systems on a single host kernel using namespaces and cgroups.
Addresses below are RFC 5737 documentation ranges or placeholders - swap in your own.
Table of Contents#
- Overview
- Architecture
- Installation
- Container Management
- Networking
- 5.1 Bridge Networking
- 5.2 NAT Networking
- 5.3 Macvlan
- Resource Management
- 6.1 CPU Limits
- 6.2 Memory Limits
- 6.3 I/O Limits
- 6.4 Device Access
- Security
- Advanced Configuration
- Troubleshooting
1. Overview#
Linux Containers (LXC) provide lightweight, OS-level virtualization by leveraging Linux kernel features to run multiple isolated user-space instances (containers) on a single host. Unlike virtual machines, containers share the host kernel, resulting in near-native performance and minimal overhead.
- Project Homepage: linuxcontainers.org/lxc
- Documentation: linuxcontainers.org/lxc/documentation
- License: LGPL 2.1+
LXC vs. related technologies:
| Feature | LXC | Docker | VM (KVM/QEMU) |
|---|---|---|---|
| Isolation level | OS-level (shared kernel) | OS-level (shared kernel) | Hardware-level (separate kernel) |
| Startup time | Seconds | Seconds | Minutes |
| Overhead | Minimal | Minimal | Significant |
| Use case | Full system containers | Application containers | Full OS with different kernels |
| Init system | Runs systemd/OpenRC | Single process (typically) | Full init |
| Filesystem | Persistent rootfs | Layered images | Disk image |
2. Architecture#
2.1 Namespaces#
Namespaces partition kernel resources so that each container sees its own isolated instance:
| Namespace | Flag | Isolates |
|---|---|---|
| PID | CLONE_NEWPID | Process IDs |
| NET | CLONE_NEWNET | Network interfaces, routing tables, iptables |
| MNT | CLONE_NEWNS | Mount points |
| UTS | CLONE_NEWUTS | Hostname and domain name |
| IPC | CLONE_NEWIPC | System V IPC, POSIX message queues |
| USER | CLONE_NEWUSER | User and group IDs |
| CGROUP | CLONE_NEWCGROUP | cgroup root directory |
| TIME | CLONE_NEWTIME | Boot and monotonic clocks (kernel 5.6+) |
2.2 Control Groups (cgroups)#
cgroups allow the kernel to limit, prioritize, and account for resource usage (CPU, memory, disk I/O, network) on a per-container basis. Modern distributions use cgroup v2 (unified hierarchy), which LXC fully supports.
The cgroup filesystem is mounted at /sys/fs/cgroup/. In cgroup v2, all controllers share a single hierarchy:
/sys/fs/cgroup/
lxc/
my-container/
cgroup.controllers
cpu.max
memory.max
io.max
pids.max2.3 Overlay Filesystems#
Overlay filesystems layer a writable upper directory on top of a read-only lower directory. This allows multiple containers to share a common base image while maintaining independent writable layers, reducing disk usage and speeding up container creation.
3. Installation#
3.1 Prerequisites#
- Linux kernel 4.4 or newer (5.x+ recommended for full cgroup v2 and user namespace support)
- cgroup v2 enabled (default on most distributions since 2020)
- sudo or root access
3.2 Package Installation#
# Arch Linux
sudo pacman -S lxc
# Debian / Ubuntu
sudo apt update
sudo apt install lxc lxc-templates
# RHEL / Fedora
sudo dnf install lxc lxc-templates lxc-extra
# openSUSE
sudo zypper install lxcVerify the installation:
lxc-checkconfigThis checks kernel configuration for namespace and cgroup support. All items should show "enabled".
3.3 Verifying cgroup v2#
# Check if cgroup v2 is active
mount | grep cgroup2
# Expected output: cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
# Or check the filesystem type
stat -f /sys/fs/cgroup/ | grep Type
# Should show: Type: cgroup2fsIf your system still uses cgroup v1 (hybrid or legacy), you can switch to v2 by adding systemd.unified_cgroup_hierarchy=1 to your kernel command line.
4. Container Management#
4.1 Creating Containers#
# Create using the download template (interactive, selects distro/release/arch)
sudo lxc-create -n <container> -t download
# Create a specific distribution directly
sudo lxc-create -n <container> -t download -- -d alpine -r 3.19 -a amd64
sudo lxc-create -n <container> -t download -- -d debian -r bookworm -a amd64
sudo lxc-create -n <container> -t download -- -d ubuntu -r noble -a amd64
sudo lxc-create -n <container> -t download -- -d archlinux -r current -a amd64
# List available images
sudo lxc-create -n temp -t download -- --listContainer rootfs and configuration are stored in:
- Privileged:
/var/lib/lxc/<container>/ - Unprivileged:
~/.local/share/lxc/<container>/
4.2 Lifecycle Commands#
| Command | Description |
|---|---|
sudo lxc-start -n <container> | Start a container in the background |
sudo lxc-start -n <container> -F | Start in the foreground (attached to console) |
sudo lxc-stop -n <container> | Gracefully stop a container |
sudo lxc-stop -n <container> -k | Force kill a container |
sudo lxc-restart -n <container> | Restart a container |
sudo lxc-freeze -n <container> | Suspend all processes in a container |
sudo lxc-unfreeze -n <container> | Resume a frozen container |
sudo lxc-destroy -n <container> | Delete a container and its rootfs |
sudo lxc-destroy -n <container> -f | Force-destroy a running container |
4.3 Listing and Inspecting#
# List all containers with status
sudo lxc-ls --fancy
# Show detailed container info
sudo lxc-info -n <container>
# Show container configuration
sudo lxc-config -n <container> -l
# Show container cgroup usage
sudo lxc-cgroup -n <container> memory.current
sudo lxc-cgroup -n <container> cpu.stat4.4 Accessing Containers#
# Attach to a running container (login prompt)
sudo lxc-console -n <container>
# Detach with: Ctrl+A, Q
# Execute a command inside a container
sudo lxc-attach -n <container> -- <command>
# Get a root shell
sudo lxc-attach -n <container>
# Execute as a specific user
sudo lxc-attach -n <container> -- su - <username>4.5 Cloning and Snapshots#
# Clone a container (copy)
sudo lxc-copy -n <source> -N <destination>
# Clone using overlayfs (fast, shared base)
sudo lxc-copy -n <source> -N <destination> -B overlayfs
# Create a snapshot
sudo lxc-snapshot -n <container>
# List snapshots
sudo lxc-snapshot -n <container> -L
# Restore from snapshot
sudo lxc-snapshot -n <container> -r <snapshot_name>
# Destroy a snapshot
sudo lxc-snapshot -n <container> -d <snapshot_name>5. Networking#
5.1 Bridge Networking#
The most common setup; containers connect to a Linux bridge on the host:
# /var/lib/lxc/<container>/config (or container-specific config)
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:xx:xx:xxCreate the bridge (if not auto-created by LXC):
sudo ip link add lxcbr0 type bridge
sudo ip addr add 192.0.2.1/24 dev lxcbr0
sudo ip link set lxcbr0 upFor DHCP on the bridge, install and configure dnsmasq:
sudo dnsmasq --interface=lxcbr0 --dhcp-range=192.0.2.2,192.0.2.254,12h --no-daemon5.2 NAT Networking#
Enable NAT so containers can reach external networks through the host:
# Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-lxc-forward.conf
# Add NAT rule
sudo iptables -t nat -A POSTROUTING -s 192.0.2.0/24 ! -d 192.0.2.0/24 -j MASQUERADE
# Or with nftables
sudo nft add table nat
sudo nft add chain nat postrouting { type nat hook postrouting priority 100 \; }
sudo nft add rule nat postrouting ip saddr 192.0.2.0/24 ip daddr != 192.0.2.0/24 masquerade5.3 Macvlan#
Containers appear as physical devices on the LAN with their own MAC addresses:
lxc.net.0.type = macvlan
lxc.net.0.macvlan.mode = bridge
lxc.net.0.link = eth0
lxc.net.0.flags = up
lxc.net.0.ipv4.address = 198.51.100.100/24
lxc.net.0.ipv4.gateway = 198.51.100.1Note: With macvlan, the host cannot communicate with the container directly. Use a separate bridge or vepa mode if host-container communication is needed.
6. Resource Management#
All examples use cgroup v2 syntax. The configuration file is /var/lib/lxc/<container>/config.
6.1 CPU Limits#
# Limit to 50% of one CPU core (50ms out of every 100ms period)
lxc.cgroup2.cpu.max = 50000 100000
# Pin to specific CPU cores (cores 0 and 2)
lxc.cgroup2.cpuset.cpus = 0,2
# Relative CPU weight (1-10000, default 100)
lxc.cgroup2.cpu.weight = 506.2 Memory Limits#
# Hard memory limit (container is OOM-killed if exceeded)
lxc.cgroup2.memory.max = 512M
# Soft memory limit (reclaim target under pressure)
lxc.cgroup2.memory.low = 256M
# Memory + swap limit (set equal to memory.max to disable swap)
lxc.cgroup2.memory.swap.max = 0
# High watermark (triggers reclaim, not a hard limit)
lxc.cgroup2.memory.high = 480M6.3 I/O Limits#
# Limit read/write bandwidth on a specific device (major:minor)
# Find the device numbers with: ls -la /dev/sda -> 8:0
lxc.cgroup2.io.max = 8:0 rbps=10485760 wbps=5242880
# Limit IOPS
lxc.cgroup2.io.max = 8:0 riops=1000 wiops=500
# I/O weight (1-10000, default 100)
lxc.cgroup2.io.weight = 506.4 Device Access#
# PID limit
lxc.cgroup2.pids.max = 256
# Allow access to a specific device
lxc.cgroup2.devices.allow = c 10:200 rwm
# Deny all device access, then whitelist
lxc.cgroup2.devices.deny = a
lxc.cgroup2.devices.allow = c 1:3 rwm # /dev/null
lxc.cgroup2.devices.allow = c 1:5 rwm # /dev/zero
lxc.cgroup2.devices.allow = c 5:0 rwm # /dev/tty
lxc.cgroup2.devices.allow = c 5:1 rwm # /dev/console7. Security#
7.1 Privileged vs. Unprivileged Containers#
| Type | Root in Container | Security | Performance |
|---|---|---|---|
| Privileged | Maps to host root (UID 0) | Lower; root escape is more impactful | Native |
| Unprivileged | Maps to unprivileged host UID (e.g., 100000) | Higher; root inside cannot affect the host | Near-native |
Always prefer unprivileged containers unless a specific use case requires privileged access.
7.2 User Namespace Mapping#
For unprivileged containers, map container UIDs/GIDs to high-numbered host UIDs/GIDs:
# /var/lib/lxc/<container>/config
lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536This maps container UID 0-65535 to host UID 100000-165535.
Configure allowed subordinate ID ranges:
# /etc/subuid
root:100000:65536
# /etc/subgid
root:100000:65536For user-level (non-root) LXC, add your user:
# /etc/subuid
myuser:100000:65536
# /etc/subgid
myuser:100000:655367.3 AppArmor and SELinux#
# AppArmor (default on Ubuntu/Debian)
lxc.apparmor.profile = generated
# Or use a custom profile
lxc.apparmor.profile = lxc-container-default-with-nesting
# SELinux (default on RHEL/Fedora)
lxc.selinux.context = system_u:system_r:lxc_t:s0:c62,c987.4 Seccomp Profiles#
Restrict which system calls are available inside the container:
# Use the default restrictive profile
lxc.seccomp.profile = /usr/share/lxc/config/common.seccomp
# Or a custom profile
lxc.seccomp.profile = /etc/lxc/my-seccomp.conf7.5 Capability Dropping#
Drop Linux capabilities to reduce the attack surface:
# Drop specific capabilities
lxc.cap.drop = sys_admin
lxc.cap.drop = sys_module
lxc.cap.drop = mac_admin
lxc.cap.drop = mac_override
# Or keep only specific capabilities
lxc.cap.keep = net_admin net_bind_service sys_ptrace8. Advanced Configuration#
8.1 Custom Container Images#
Create a container, customize it, and use it as a template:
# Create and configure a base container
sudo lxc-create -n base-template -t download -- -d debian -r bookworm -a amd64
sudo lxc-start -n base-template
sudo lxc-attach -n base-template -- apt update
sudo lxc-attach -n base-template -- apt install -y nginx curl vim
sudo lxc-stop -n base-template
# Clone it for new instances
sudo lxc-copy -n base-template -N web-server-01
sudo lxc-copy -n base-template -N web-server-028.2 Autostart#
Configure containers to start automatically on host boot:
# /var/lib/lxc/<container>/config
lxc.start.auto = 1
lxc.start.delay = 5 # Wait 5 seconds before starting the next container
lxc.start.order = 100 # Higher values start first# List autostart containers
sudo lxc-autostart --list8.3 Shared Directories#
Mount host directories inside containers:
# Read-write bind mount
lxc.mount.entry = /host/shared /var/lib/lxc/<container>/rootfs/mnt/shared none bind 0 0
# Read-only bind mount
lxc.mount.entry = /host/data /var/lib/lxc/<container>/rootfs/mnt/data none bind,ro 0 09. Troubleshooting#
| Issue | Cause | Solution |
|---|---|---|
lxc-checkconfig shows items as "missing" | Kernel not compiled with required options | Use a distribution kernel (4.4+) or recompile with namespace and cgroup support |
lxc-start fails with permission denied | Unprivileged container without proper subuid/subgid | Configure /etc/subuid and /etc/subgid, ensure lxc.idmap is set |
| Container has no network | Bridge not configured or not up | Verify lxcbr0 exists and is up; check lxc.net.0.type in container config |
| Cannot access internet from container | IP forwarding or NAT not configured | Enable net.ipv4.ip_forward=1 and add a MASQUERADE iptables/nftables rule |
| cgroup settings ignored | Using cgroup v1 syntax on a cgroup v2 system | Replace lxc.cgroup. with lxc.cgroup2. in container config |
| Unprivileged container start fails | Insufficient subordinate UID/GID range | Verify /etc/subuid and /etc/subgid have at least 65536 IDs allocated |
lxc-create download fails | Network issue or image server unreachable | Check internet connectivity; try a different mirror with --server |
| Container fails to start after host reboot | Filesystem not mounted or bridge not created | Ensure bridge creation is persistent (via systemd-networkd or netplan); check autostart config |
Operation not permitted inside container | Dropped capabilities or seccomp blocking | Review lxc.cap.drop and lxc.seccomp.profile; adjust as needed |
| Nested containers fail | Nesting not enabled | Add lxc.include = /usr/share/lxc/config/nesting.conf to the outer container config |