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#

  1. Overview
  2. Architecture
  3. Installation
  4. Container Management
  5. Networking
  6. Resource Management
  7. Security
  8. Advanced Configuration
  9. 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.

LXC vs. related technologies:

FeatureLXCDockerVM (KVM/QEMU)
Isolation levelOS-level (shared kernel)OS-level (shared kernel)Hardware-level (separate kernel)
Startup timeSecondsSecondsMinutes
OverheadMinimalMinimalSignificant
Use caseFull system containersApplication containersFull OS with different kernels
Init systemRuns systemd/OpenRCSingle process (typically)Full init
FilesystemPersistent rootfsLayered imagesDisk image

2. Architecture#

2.1 Namespaces#

Namespaces partition kernel resources so that each container sees its own isolated instance:

NamespaceFlagIsolates
PIDCLONE_NEWPIDProcess IDs
NETCLONE_NEWNETNetwork interfaces, routing tables, iptables
MNTCLONE_NEWNSMount points
UTSCLONE_NEWUTSHostname and domain name
IPCCLONE_NEWIPCSystem V IPC, POSIX message queues
USERCLONE_NEWUSERUser and group IDs
CGROUPCLONE_NEWCGROUPcgroup root directory
TIMECLONE_NEWTIMEBoot 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.max

2.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 lxc

Verify the installation:

lxc-checkconfig

This 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: cgroup2fs

If 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 -- --list

Container rootfs and configuration are stored in:

  • Privileged: /var/lib/lxc/<container>/
  • Unprivileged: ~/.local/share/lxc/<container>/

4.2 Lifecycle Commands#

CommandDescription
sudo lxc-start -n <container>Start a container in the background
sudo lxc-start -n <container> -FStart in the foreground (attached to console)
sudo lxc-stop -n <container>Gracefully stop a container
sudo lxc-stop -n <container> -kForce 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> -fForce-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.stat

4.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:xx

Create 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 up

For DHCP on the bridge, install and configure dnsmasq:

sudo dnsmasq --interface=lxcbr0 --dhcp-range=192.0.2.2,192.0.2.254,12h --no-daemon

5.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 masquerade

5.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.1

Note: 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 = 50

6.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 = 480M

6.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 = 50

6.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/console

7. Security#

7.1 Privileged vs. Unprivileged Containers#

TypeRoot in ContainerSecurityPerformance
PrivilegedMaps to host root (UID 0)Lower; root escape is more impactfulNative
UnprivilegedMaps to unprivileged host UID (e.g., 100000)Higher; root inside cannot affect the hostNear-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 65536

This maps container UID 0-65535 to host UID 100000-165535.

Configure allowed subordinate ID ranges:

# /etc/subuid
root:100000:65536

# /etc/subgid
root:100000:65536

For user-level (non-root) LXC, add your user:

# /etc/subuid
myuser:100000:65536

# /etc/subgid
myuser:100000:65536

7.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,c98

7.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.conf

7.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_ptrace

8. 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-02

8.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 --list

8.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 0

9. Troubleshooting#

IssueCauseSolution
lxc-checkconfig shows items as "missing"Kernel not compiled with required optionsUse a distribution kernel (4.4+) or recompile with namespace and cgroup support
lxc-start fails with permission deniedUnprivileged container without proper subuid/subgidConfigure /etc/subuid and /etc/subgid, ensure lxc.idmap is set
Container has no networkBridge not configured or not upVerify lxcbr0 exists and is up; check lxc.net.0.type in container config
Cannot access internet from containerIP forwarding or NAT not configuredEnable net.ipv4.ip_forward=1 and add a MASQUERADE iptables/nftables rule
cgroup settings ignoredUsing cgroup v1 syntax on a cgroup v2 systemReplace lxc.cgroup. with lxc.cgroup2. in container config
Unprivileged container start failsInsufficient subordinate UID/GID rangeVerify /etc/subuid and /etc/subgid have at least 65536 IDs allocated
lxc-create download failsNetwork issue or image server unreachableCheck internet connectivity; try a different mirror with --server
Container fails to start after host rebootFilesystem not mounted or bridge not createdEnsure bridge creation is persistent (via systemd-networkd or netplan); check autostart config
Operation not permitted inside containerDropped capabilities or seccomp blockingReview lxc.cap.drop and lxc.seccomp.profile; adjust as needed
Nested containers failNesting not enabledAdd lxc.include = /usr/share/lxc/config/nesting.conf to the outer container config

See Also#

Sources#