LXC Containers: Lightweight Virtualization
System containers on Proxmox and standalone. Resource efficiency with near-native performance.
Linux Containers (LXC) provide OS-level virtualization that’s incredibly efficient for homelabs. Unlike full VMs, LXC containers share the host kernel while maintaining isolation, resulting in minimal overhead and near-native performance. This guide covers both Proxmox LXC and standalone LXD implementations.
Understanding LXC vs LXD vs Docker
LXC (Linux Containers)
- Low-level container runtime
- System containers (full OS environment)
- Ideal for running complete Linux distributions
- Direct management via command-line tools
LXD (LXC Daemon)
- High-level container and VM manager built on LXC
- User-friendly management interface
- Advanced features: snapshots, live migration, clustering
- Built-in image server
Docker
- Application containers (single process)
- Immutable, stateless design
- Great for microservices
- Less suitable for traditional system administration
When to Use LXC/LXD:
- Running full Linux environments
- Hosting multiple services in isolated systems
- Development and testing environments
- Lightweight alternative to VMs
- When you need systemd, cron, SSH, and traditional system tools
When to Use Docker:
- Microservices and cloud-native applications
- CI/CD pipelines
- Application deployment and scaling
- Container orchestration with Kubernetes
Part 1: LXC on Proxmox VE
Proxmox provides excellent LXC container support with web-based management.
Prerequisites
# Ensure Proxmox is up to date
apt update && apt dist-upgrade
# Verify LXC is installed (it should be by default)
pct list Creating Your First Container
Via Web UI:
Click “Create CT” in top-right
General Tab:
- Node: Select your Proxmox node
- CT ID: 100 (or auto)
- Hostname: ubuntu-web-01
- Password: Set root password
- SSH public key: Paste your public key (recommended)
- Unprivileged container: ✓ (checked - more secure)
Template Tab:
- Storage: local
- Template: ubuntu-22.04-standard (download if needed)
Disks Tab:
- Storage: local-lvm
- Disk size: 8GB (adjust as needed)
CPU Tab:
- Cores: 2
Memory Tab:
- Memory: 2048 MB
- Swap: 512 MB
Network Tab:
- Bridge: vmbr0
- IPv4: DHCP or static
- IPv6: auto (or disabled)
Click “Finish”
CLI Container Creation
# Download template
pveam update
pveam available
pveam download local ubuntu-22.04-standard_22.04-1_amd64.tar.zst
# Create container
pct create 100 local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst \
--hostname ubuntu-web-01 \
--cores 2 \
--memory 2048 \
--swap 512 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--storage local-lvm \
--rootfs local-lvm:8 \
--unprivileged 1 \
--features nesting=1 \
--password
# Start container
pct start 100
# Enter container
pct enter 100
# Check status
pct status 100 Container Configuration File
# View container config
cat /etc/pve/lxc/100.conf
# Example configuration:
arch: amd64
cores: 2
hostname: ubuntu-web-01
memory: 2048
net0: name=eth0,bridge=vmbr0,hwaddr=XX:XX:XX:XX:XX:XX,ip=dhcp,type=veth
ostype: ubuntu
rootfs: local-lvm:vm-100-disk-0,size=8G
swap: 512
unprivileged: 1
features: nesting=1 Essential Container Operations
# Start/Stop/Restart
pct start 100
pct stop 100
pct shutdown 100
pct reboot 100
# Enter container (attach to console)
pct enter 100
# Execute commands in container
pct exec 100 -- apt update
pct exec 100 -- systemctl status nginx
# Container status
pct status 100
pct list
# Resource monitoring
pct df 100
pct mount 100 Container Features and Capabilities
# Edit container config
nano /etc/pve/lxc/100.conf
# Useful features:
features: nesting=1,keyctl=1,fuse=1,mknod=1
# Feature explanations:
# nesting=1 : Run Docker/LXC inside container
# keyctl=1 : Allows keyctl syscalls (systemd requirements)
# fuse=1 : FUSE filesystem support
# mknod=1 : Allow device node creation Privileged vs Unprivileged Containers
Unprivileged (Recommended):
unprivileged: 1
# Benefits:
# - More secure (UID mapping)
# - Container root != host root
# - Reduced attack surface
# Limitations:
# - Some features require lxc.idmap configuration
# - NFSv3 may not work without special configuration Privileged:
unprivileged: 0
# Use cases:
# - Direct hardware access needed
# - Legacy applications
# - NFS shares
# Security risk:
# - Container root = host root
# - Container escape = host compromise UID/GID Mapping for Unprivileged Containers
# View container's UID/GID mapping
cat /etc/subuid
cat /etc/subgid
# Example:
# root:100000:65536
# This maps container UID 0-65535 to host UID 100000-165535
# Custom mapping for accessing host resources
nano /etc/pve/lxc/100.conf
# Add UID mapping (map container UID 1000 to host UID 1000)
lxc.idmap: u 0 100000 1000
lxc.idmap: u 1000 1000 1
lxc.idmap: u 1001 101001 64535
lxc.idmap: g 0 100000 1000
lxc.idmap: g 1000 1000 1
lxc.idmap: g 1001 101001 64535
# Restart container
pct stop 100
pct start 100 Mount Host Directories in Container
# Method 1: Bind mount (in container config)
nano /etc/pve/lxc/100.conf
# Add mount point
mp0: /mnt/data,mp=/data,backup=0
# Method 2: Manual mount point
pct set 100 -mp0 /mnt/nas/media,mp=/media,backup=0
# Method 3: fstab (inside container - not recommended)
# Better to use Proxmox mount points
# Verify
pct mount 100
ls -la /var/lib/lxc/100/rootfs/data Networking Configurations
DHCP (Simplest):
net0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth Static IP:
net0: name=eth0,bridge=vmbr0,ip=192.168.1.100/24,gw=192.168.1.1,type=veth
# DNS configuration (inside container)
pct enter 100
echo "nameserver 1.1.1.1" > /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /etc/resolv.conf Multiple Network Interfaces:
net0: name=eth0,bridge=vmbr0,ip=192.168.1.100/24,gw=192.168.1.1,type=veth
net1: name=eth1,bridge=vmbr1,ip=10.0.0.100/24,type=veth VLAN Tagging:
net0: name=eth0,bridge=vmbr0,tag=10,ip=192.168.10.100/24,gw=192.168.10.1,type=veth MAC Address Control:
net0: name=eth0,bridge=vmbr0,hwaddr=AA:BB:CC:DD:EE:FF,ip=dhcp,type=veth Container Templates and Distributions
# Update template list
pveam update
# List available templates
pveam available
# Popular templates:
# - ubuntu-22.04-standard
# - debian-12-standard
# - alpine-3.18-default (minimal)
# - rocky-9-default (RHEL-like)
# - archlinux-base
# Download template
pveam download local ubuntu-22.04-standard_22.04-1_amd64.tar.zst
# List downloaded templates
pveam list local
# Remove old templates
pveam remove local:vztmpl/old-template.tar.zst Part 2: Advanced Proxmox LXC Features
Container Snapshots
# Create snapshot
pct snapshot 100 snapshot-before-upgrade
# List snapshots
pct listsnapshot 100
# Rollback to snapshot
pct rollback 100 snapshot-before-upgrade
# Delete snapshot
pct delsnapshot 100 snapshot-before-upgrade Container Backup and Restore
# Backup container
vzdump 100 --storage local --mode snapshot --compress zstd
# Backup to specific location
vzdump 100 --dumpdir /mnt/backups --compress zstd
# Restore container
pct restore 101 /var/lib/vz/dump/vzdump-lxc-100-2025_12_17-10_00_00.tar.zst \
--storage local-lvm
# Restore with different settings
pct restore 101 /path/to/backup.tar.zst \
--storage local-lvm \
--hostname ubuntu-web-02 \
--rootfs local-lvm:8 Container Migration
# Offline migration
pct stop 100
pct migrate 100 target-node --storage target-storage
# Online migration (requires shared storage)
pct migrate 100 target-node --online
# Migration with storage change
pct migrate 100 target-node --storage local-lvm:vm-100-disk-0=target-storage Container Resource Limits
# CPU limits
pct set 100 --cores 2
pct set 100 --cpulimit 1.5 # 1.5 cores max
pct set 100 --cpuunits 1024 # CPU share weight
# Memory limits
pct set 100 --memory 2048
pct set 100 --swap 512
# Disk I/O limits
pct set 100 --rootfs local-lvm:8,mbps_r=100,mbps_w=50
# Network rate limiting
pct set 100 --net0 name=eth0,bridge=vmbr0,rate=100 # 100 Mbps
# View current limits
pct config 100 Nested Docker in LXC
# Enable nesting in container config
pct set 100 --features nesting=1,keyctl=1
# Inside container
pct enter 100
# Update system
apt update && apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
# Verify Docker works
docker run hello-world
# Note: For unprivileged containers, you may need:
# lxc.apparmor.profile: unconfined
# in /etc/pve/lxc/100.conf (reduces security) Container Hooks and Automation
# Create hook script
nano /var/lib/vz/snippets/container-start-hook.sh
#!/bin/bash
# Hook script for container start
VMID=$1
PHASE=$2
if [ "$PHASE" == "pre-start" ]; then
echo "Container $VMID is starting..."
# Pre-start actions
fi
if [ "$PHASE" == "post-start" ]; then
echo "Container $VMID has started"
# Post-start actions
fi
# Make executable
chmod +x /var/lib/vz/snippets/container-start-hook.sh
# Add to container config
nano /etc/pve/lxc/100.conf
# Add hook
hookscript: local:snippets/container-start-hook.sh Part 3: Standalone LXD (Non-Proxmox)
LXD provides a powerful container management experience on standalone Ubuntu/Debian systems.
Installation
Ubuntu 22.04/24.04:
# Install LXD snap (recommended)
sudo snap install lxd
# Initialize LXD
sudo lxd init
# Interactive prompts:
# - Clustering: no
# - Storage backend: dir or zfs
# - Network bridge: yes
# - Bridge name: lxdbr0
# - IPv4: auto
# - IPv6: auto (or none)
# - LXD remote: no Debian 12:
# Install dependencies
sudo apt update
sudo apt install snapd
sudo snap install core
sudo snap install lxd
# Add user to lxd group
sudo usermod -aG lxd $USER
newgrp lxd
# Initialize
lxd init LXD Configuration Options
# Manual configuration
lxd init
# Questions and recommended answers:
# Would you like to use LXD clustering? no
# Storage backend: zfs (best) or dir (simple)
# Create new ZFS pool? yes
# Size in GB: 50GB (or use existing disk)
# Network bridge: yes
# Bridge interface name: lxdbr0
# IPv4 address: auto
# IPv6 address: auto (or none)
# LXD available over network? no
# Update image cache? yes Creating Containers with LXD
# List available images
lxc image list images: | grep ubuntu
# Launch Ubuntu 22.04
lxc launch images:ubuntu/22.04 web01
# Launch with specific configuration
lxc launch images:ubuntu/22.04 web02 \
--config limits.cpu=2 \
--config limits.memory=2GB \
--device root,size=10GB
# Launch Alpine (minimal)
lxc launch images:alpine/3.18 alpine01
# Launch from specific architecture
lxc launch images:ubuntu/22.04/arm64 arm-container LXD Container Management
# List containers
lxc list
# Start/Stop
lxc start web01
lxc stop web01
lxc restart web01
# Container info
lxc info web01
# Execute commands
lxc exec web01 -- apt update
lxc exec web01 -- bash
# Shell access
lxc shell web01
# View logs
lxc console web01 --show-log
# Delete container
lxc stop web01
lxc delete web01
# Force delete
lxc delete web01 --force LXD Profiles
Profiles define reusable container configurations:
# List profiles
lxc profile list
# View default profile
lxc profile show default
# Create custom profile
lxc profile create webserver
# Edit profile
lxc profile edit webserver
# Example webserver profile:
config:
limits.cpu: "2"
limits.memory: 2GB
security.nesting: "true"
description: Web server profile
devices:
eth0:
name: eth0
network: lxdbr0
type: nic
root:
path: /
pool: default
size: 10GB
type: disk
shared:
path: /var/www/shared
source: /srv/www/shared
type: disk
# Apply profile to container
lxc launch images:ubuntu/22.04 web03 --profile webserver
# Apply profile to existing container
lxc profile assign web01 webserver
# Apply multiple profiles
lxc profile assign web01 default,webserver LXD Networking
Default Bridge:
# View network config
lxc network show lxdbr0
# Edit network
lxc network edit lxdbr0
# Example config:
config:
ipv4.address: 10.100.0.1/24
ipv4.nat: "true"
ipv6.address: none Create Additional Networks:
# Create isolated network
lxc network create dmz \
ipv4.address=10.200.0.1/24 \
ipv4.nat=true \
ipv6.address=none
# Attach container to network
lxc network attach dmz web01 eth1
# Configure static IP
lxc config device set web01 eth0 ipv4.address=10.100.0.50 MacVLAN for Direct Network Access:
# Create macvlan network
lxc network create macvlan-net \
--type=macvlan \
parent=eth0
# Launch container on macvlan
lxc launch images:ubuntu/22.04 direct-net
lxc config device add direct-net eth0 nic \
nictype=macvlan \
parent=eth0 LXD Storage Management
# List storage pools
lxc storage list
# Create ZFS pool
lxc storage create pool-zfs zfs size=100GB
# Create directory pool
lxc storage create pool-dir dir
# View storage info
lxc storage info pool-zfs
# Create custom volume
lxc storage volume create pool-zfs data-volume size=50GB
# Attach volume to container
lxc storage volume attach pool-zfs data-volume web01 /data
# Storage pool for images
lxc storage create images-pool dir
lxc config set images.storage images-pool LXD Snapshots and Backups
# Create snapshot
lxc snapshot web01 snap-before-update
# List snapshots
lxc info web01 | grep Snapshots -A 10
# Restore snapshot
lxc restore web01 snap-before-update
# Delete snapshot
lxc delete web01/snap-before-update
# Export container (backup)
lxc export web01 web01-backup.tar.gz
# Import container
lxc import web01-backup.tar.gz web01-restored
# Scheduled snapshots
lxc config set web01 snapshots.schedule "@daily"
lxc config set web01 snapshots.expiry "7d" LXD Copy and Migration
# Copy container locally
lxc copy web01 web01-clone
# Copy container to remote
lxc remote add remote-host REMOTE-IP
lxc copy web01 remote-host:web01
# Move container (migration)
lxc move web01 web01-renamed
# Move to remote
lxc move web01 remote-host:web01
# Live migration (requires shared storage or criu)
lxc move web01 remote-host:web01 --mode=pull Part 4: Advanced LXD Features
LXD Clustering
# Initialize first node
lxd init
# Choose: Would you like to use LXD clustering? yes
# Cluster name: homelab-cluster
# Node name: node1
# Join additional nodes
# On node2:
lxd init
# Choose clustering: yes
# Join existing cluster
# Provide node1 IP and join token
# View cluster members
lxc cluster list
# Launch container on specific node
lxc launch images:ubuntu/22.04 web01 --target node2 Container Resource Monitoring
# Real-time stats
lxc monitor
# Container resource usage
lxc info web01 --resources
# Detailed metrics
lxc monitor web01 --type=logging
# CPU usage
lxc exec web01 -- top
# Memory usage
lxc exec web01 -- free -h Security Hardening
AppArmor Profiles:
# View current profile
lxc config get web01 security.apparmor
# Set custom profile
lxc config set web01 security.apparmor.profile=container-restrictive
# Disable (not recommended)
lxc config set web01 security.apparmor.profile=unconfined Seccomp Profiles:
# Default seccomp enabled
lxc config get web01 security.seccomp
# Custom seccomp profile
lxc config set web01 security.seccomp.profile=/path/to/profile.json Privileged vs Unprivileged:
# Check if privileged
lxc config get web01 security.privileged
# Create privileged container (not recommended)
lxc launch images:ubuntu/22.04 priv-container -c security.privileged=true
# Always prefer unprivileged for security Resource Limits and QoS
# CPU limits
lxc config set web01 limits.cpu=2
lxc config set web01 limits.cpu.allowance=50% # 50% of one core
lxc config set web01 limits.cpu.priority=5 # 0-10, higher = more priority
# Memory limits
lxc config set web01 limits.memory=2GB
lxc config set web01 limits.memory.enforce=hard
lxc config set web01 limits.memory.swap=false
# Disk I/O limits
lxc config device set web01 root limits.read=20MB
lxc config device set web01 root limits.write=10MB
# Network bandwidth limits
lxc config device set web01 eth0 limits.ingress=100Mbit
lxc config device set web01 eth0 limits.egress=50Mbit
# Process limits
lxc config set web01 limits.processes=500 Part 5: Practical Use Cases
Use Case 1: Development Environment
# Create development container
lxc launch images:ubuntu/22.04 dev-env
# Install development tools
lxc exec dev-env -- bash -c "
apt update && apt install -y \
git build-essential python3 python3-pip \
nodejs npm golang-go docker.io
"
# Share host code directory
lxc config device add dev-env code disk \
source=/home/user/projects \
path=/workspace
# Access container
lxc shell dev-env Use Case 2: Web Server Stack
# Create profile for web servers
lxc profile create lamp
lxc profile edit lamp << EOF
config:
limits.cpu: "2"
limits.memory: 4GB
security.nesting: "false"
devices:
eth0:
name: eth0
network: lxdbr0
type: nic
root:
path: /
pool: default
size: 20GB
type: disk
www:
path: /var/www
source: /srv/www
type: disk
EOF
# Launch web servers
lxc launch images:ubuntu/22.04 web-frontend --profile lamp
lxc launch images:ubuntu/22.04 web-backend --profile lamp
# Install LAMP stack
lxc exec web-frontend -- bash -c "
apt update && apt install -y apache2 php libapache2-mod-php
systemctl enable apache2
" Use Case 3: Database Servers
# PostgreSQL container
lxc launch images:ubuntu/22.04 postgres-db \
-c limits.memory=8GB \
-c limits.cpu=4
# Create data volume
lxc storage volume create default postgres-data size=100GB
lxc storage volume attach default postgres-data postgres-db /var/lib/postgresql
# Install PostgreSQL
lxc exec postgres-db -- bash -c "
apt update && apt install -y postgresql-14
systemctl enable postgresql
"
# MySQL container
lxc launch images:ubuntu/22.04 mysql-db \
-c limits.memory=8GB
lxc storage volume create default mysql-data size=100GB
lxc storage volume attach default mysql-data mysql-db /var/lib/mysql
lxc exec mysql-db -- bash -c "
apt update && apt install -y mysql-server
systemctl enable mysql
" Use Case 4: Docker Host in LXC
# Create container with nesting
lxc launch images:ubuntu/22.04 docker-host \
-c security.nesting=true \
-c security.syscalls.intercept.mknod=true
# Install Docker
lxc exec docker-host -- bash -c "
curl -fsSL https://get.docker.com | sh
systemctl enable docker
"
# Use Docker in container
lxc shell docker-host
docker run -d -p 80:80 nginx Part 6: Troubleshooting
Container Won’t Start
# Check status
lxc info web01
# View detailed logs
lxc console web01 --show-log
# Check for errors
journalctl -xe | grep lxc
# Verify storage
lxc storage list
df -h
# Check network
lxc network list
ip a
# Reset container
lxc stop web01 --force
lxc start web01 Networking Issues
# Verify bridge exists
ip link show lxdbr0
# Check IP forwarding
sysctl net.ipv4.ip_forward
# Enable if disabled
sudo sysctl -w net.ipv4.ip_forward=1
# Make permanent
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
# Check NAT rules
sudo iptables -t nat -L
# Restart network
lxc network stop lxdbr0
lxc network start lxdbr0 Storage Issues
# Check storage pool health (ZFS)
sudo zpool status
# Check disk space
lxc storage info default
# Repair ZFS pool if needed
sudo zpool scrub poolname
# Check directory permissions
ls -la /var/lib/lxd/storage-pools/ Performance Issues
# Check resource usage
lxc monitor
# Check host resources
top
htop
iotop
# Review container limits
lxc config show web01
# Check I/O wait
iostat -x 1
# Review ZFS ARC usage (if using ZFS)
cat /proc/spl/kstat/zfs/arcstats Part 7: Best Practices
Container Organization
# Use consistent naming
# Format: service-environment-number
# Examples:
lxc launch images:ubuntu/22.04 web-prod-01
lxc launch images:ubuntu/22.04 web-prod-02
lxc launch images:ubuntu/22.04 db-prod-01
lxc launch images:ubuntu/22.04 web-dev-01 Backup Strategy
# Automated daily backups
crontab -e
# Add:
0 2 * * * lxc export web01 /backups/web01-$(date +%Y%m%d).tar.gz
0 2 * * * lxc export db01 /backups/db01-$(date +%Y%m%d).tar.gz
# Retention: keep 7 daily, 4 weekly, 12 monthly
# Use backup rotation scripts Resource Planning
For a 64GB RAM, 16-core server:
- Host: 8GB RAM, 2 cores reserved
- Web containers: 2GB RAM, 2 cores each (x4) = 8GB, 8 cores
- Database containers: 8GB RAM, 4 cores each (x2) = 16GB, 8 cores
- Application containers: 4GB RAM, 2 cores each (x4) = 16GB, 8 cores
- Total allocated: 48GB RAM, 26 vCPUs (oversubscription is normal)
Security Checklist
- Always use unprivileged containers when possible
- Keep AppArmor and Seccomp enabled
- Use separate networks for DMZ and internal services
- Implement resource limits to prevent resource exhaustion
- Regular security updates:
lxc exec CONTAINER -- apt update && apt upgrade - Disable unnecessary container features
- Use SSH keys instead of passwords
- Implement host-level firewall rules
Monitoring and Maintenance
# Create monitoring script
cat > /usr/local/bin/lxc-health-check.sh << 'EOF'
#!/bin/bash
echo "=== LXD Health Check ==="
echo "Containers:"
lxc list -c ns4
echo ""
echo "Storage:"
lxc storage list
echo ""
echo "Networks:"
lxc network list
EOF
chmod +x /usr/local/bin/lxc-health-check.sh
# Run weekly health checks
crontab -e
0 8 * * 1 /usr/local/bin/lxc-health-check.sh | mail -s "LXD Health Report" [email protected] Comparison: LXC vs Virtual Machines
| Feature | LXC Container | Virtual Machine |
|---|---|---|
| Startup Time | 1-2 seconds | 30-60 seconds |
| Memory Overhead | ~10MB | 512MB-1GB+ |
| CPU Overhead | 1-2% | 5-10% |
| Disk Space | 100MB-1GB | 10GB-50GB |
| Isolation | OS-level | Hardware-level |
| Kernel | Shared | Separate |
| Use Case | Linux workloads | Any OS, full isolation |
Conclusion
LXC and LXD provide efficient, lightweight virtualization perfect for homelabs. Whether you’re using Proxmox’s integrated LXC or standalone LXD, containers offer excellent performance with minimal overhead.
Key Takeaways:
- LXC containers provide near-native performance
- Unprivileged containers are more secure
- LXD offers superior management compared to raw LXC
- Perfect for running multiple Linux services
- ZFS storage backend provides best features
- Snapshots and live migration are game-changers
- Use profiles for consistent container configuration
When to Choose LXC:
- Running multiple Linux services efficiently
- Development and testing environments
- When you need full OS functionality
- Resource-constrained systems
- Rapid container creation and destruction
Next Steps:
- Integrate with Terraform for IaC (infrastructure as code)
- Set up automated backups to remote storage
- Implement monitoring with Prometheus and Grafana
- Explore LXD clustering for high availability
- Combine with Ansible for configuration management
Happy containerizing!