Virtualization intermediate ⏱ 45 minutes

LXC Containers: Lightweight Virtualization

System containers on Proxmox and standalone. Resource efficiency with near-native performance.

By homelabbing.io • 2025-12-17
#lxc#containers#proxmox#lightweight#linux

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:

  1. Click “Create CT” in top-right

  2. 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)
  3. Template Tab:

    • Storage: local
    • Template: ubuntu-22.04-standard (download if needed)
  4. Disks Tab:

    • Storage: local-lvm
    • Disk size: 8GB (adjust as needed)
  5. CPU Tab:

    • Cores: 2
  6. Memory Tab:

    • Memory: 2048 MB
    • Swap: 512 MB
  7. Network Tab:

    • Bridge: vmbr0
    • IPv4: DHCP or static
    • IPv6: auto (or disabled)
  8. 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

FeatureLXC ContainerVirtual Machine
Startup Time1-2 seconds30-60 seconds
Memory Overhead~10MB512MB-1GB+
CPU Overhead1-2%5-10%
Disk Space100MB-1GB10GB-50GB
IsolationOS-levelHardware-level
KernelSharedSeparate
Use CaseLinux workloadsAny 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!