Introduction to LXD

LXD (pronounced “lex-dee”) is a next-generation system container and virtual machine manager. It offers a unified user experience around full Linux systems running inside containers or virtual machines. Unlike Docker which focuses on application containers, LXD provides system containers that behave more like traditional virtual machines but with the efficiency of containers.

What Makes LXD Special?

LXD bridges the gap between containers and virtual machines by providing:

  • System containers that run a full Linux distribution
  • Virtual machines support for complete isolation
  • REST API for programmatic management
  • Clustering capabilities for multi-node deployments
  • Live migration of containers and VMs
  • Snapshots and backups for easy recovery
  • Network and storage management built-in

Key Benefits

  • Resource Efficiency: Containers share the host kernel, using fewer resources than traditional VMs
  • Fast Boot Times: Containers start in seconds, not minutes
  • Density: Run many more containers than VMs on the same hardware
  • Flexibility: Mix containers and VMs on the same host
  • Security: Strong isolation with user namespaces and AppArmor/SELinux integration

Installation

Ubuntu/Debian Installation

LXD is available as a snap package, which is the recommended installation method:

# Install LXD snap
sudo snap install lxd

# Add your user to the lxd group
sudo usermod -a -G lxd $USER

# Log out and back in, or use newgrp
newgrp lxd

Alternative: APT Installation (Ubuntu)

# Update package list
sudo apt update

# Install LXD
sudo apt install lxd lxd-client

# Add user to lxd group
sudo usermod -a -G lxd $USER

CentOS/RHEL/Fedora Installation

# Install snapd first
sudo dnf install snapd
sudo systemctl enable --now snapd.socket

# Create symlink for classic snap support
sudo ln -s /var/lib/snapd/snap /snap

# Install LXD
sudo snap install lxd

Arch Linux Installation

# Install from AUR
yay -S lxd

# Or using pacman (if available in repos)
sudo pacman -S lxd

# Enable and start the service
sudo systemctl enable --now lxd

Initial Configuration

After installation, initialize LXD with the setup wizard:

# Run the initialization wizard
sudo lxd init

The wizard will ask several questions. Here are recommended settings for a typical setup:

Would you like to use LXD clustering? (yes/no) [default=no]: no
Do you want to configure a new storage pool? (yes/no) [default=yes]: yes
Name of the new storage pool [default=default]: default
Name of the storage backend to use (btrfs, dir, lvm, zfs) [default=zfs]: zfs
Create a new ZFS pool? (yes/no) [default=yes]: yes
Would you like to use an existing block device? (yes/no) [default=no]: no
Size in GB of the new loop device (1GB minimum) [default=30GB]: 50GB
Would you like to connect to a MAAS server? (yes/no) [default=no]: no
Would you like to create a new local network bridge? (yes/no) [default=yes]: yes
What should the new bridge be called? [default=lxdbr0]: lxdbr0
What IPv4 address should be used? [default=auto]: auto
What IPv6 address should be used? [default=auto]: auto
Would you like LXD to be available over the network? (yes/no) [default=no]: no
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]: yes
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]: yes

Verify Installation

# Check LXD status
lxc info

# List available images
lxc image list images: | head -20

# Check storage pools
lxc storage list

# Check network configuration
lxc network list

Basic LXD Operations

Creating Your First Container

# Launch an Ubuntu 22.04 container
lxc launch ubuntu:22.04 my-ubuntu

# Launch with custom configuration
lxc launch ubuntu:22.04 web-server -c limits.cpu=2 -c limits.memory=2GB

# Launch from a specific image server
lxc launch images:alpine/3.18 alpine-test

Container Management

# List containers
lxc list

# Start/stop containers
lxc start my-ubuntu
lxc stop my-ubuntu

# Execute commands in container
lxc exec my-ubuntu -- bash
lxc exec my-ubuntu -- apt update

# Copy files to/from container
lxc file push local-file.txt my-ubuntu/home/ubuntu/
lxc file pull my-ubuntu/etc/hostname ./

# Get container information
lxc info my-ubuntu

# Delete container
lxc delete my-ubuntu --force

Snapshots and Backups

# Create snapshot
lxc snapshot my-ubuntu backup-before-update

# List snapshots
lxc info my-ubuntu

# Restore from snapshot
lxc restore my-ubuntu backup-before-update

# Export container
lxc export my-ubuntu my-ubuntu-backup.tar.gz

# Import container
lxc import my-ubuntu-backup.tar.gz

Linux Lab Setup Example

Let’s create a practical Linux lab environment using LXD that includes multiple distributions and services.

Lab Architecture

Our lab will include:

  • Gateway/Router: Alpine Linux with routing capabilities
  • Web Server: Ubuntu with Nginx
  • Database Server: CentOS with MySQL
  • Development Environment: Debian with development tools
  • Monitoring: Ubuntu with basic monitoring tools

Step 1: Create the Lab Network

# Create a dedicated network for our lab
lxc network create labnet \
    ipv4.address=192.168.100.1/24 \
    ipv4.nat=true \
    ipv6.address=none

# Verify network creation
lxc network show labnet

Step 2: Launch Lab Containers

# Gateway/Router (Alpine Linux)
lxc launch images:alpine/3.18 lab-gateway \
    --network labnet \
    -c limits.cpu=1 \
    -c limits.memory=512MB

# Web Server (Ubuntu)
lxc launch ubuntu:22.04 lab-webserver \
    --network labnet \
    -c limits.cpu=2 \
    -c limits.memory=1GB

# Database Server (CentOS Stream)
lxc launch images:centos/9-Stream lab-database \
    --network labnet \
    -c limits.cpu=2 \
    -c limits.memory=2GB

# Development Environment (Debian)
lxc launch images:debian/12 lab-devenv \
    --network labnet \
    -c limits.cpu=2 \
    -c limits.memory=2GB

# Monitoring Server (Ubuntu)
lxc launch ubuntu:22.04 lab-monitoring \
    --network labnet \
    -c limits.cpu=1 \
    -c limits.memory=1GB

Step 3: Configure the Web Server

# Access the web server
lxc exec lab-webserver -- bash

# Inside the container, install and configure Nginx
apt update && apt install -y nginx
systemctl enable nginx
systemctl start nginx

# Create a simple index page
cat > /var/www/html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
    <title>Lab Web Server</title>
</head>
<body>
    <h1>Welcome to the LXD Lab Environment</h1>
    <p>This is running on Ubuntu in an LXD container</p>
    <p>Server: lab-webserver</p>
</body>
</html>
EOF

# Exit container
exit

Step 4: Configure the Database Server

# Access the database server
lxc exec lab-database -- bash

# Install MySQL (MariaDB)
dnf update -y
dnf install -y mariadb-server mariadb
systemctl enable mariadb
systemctl start mariadb

# Secure MySQL installation
mysql_secure_installation

# Create a test database
mysql -u root -p << 'EOF'
CREATE DATABASE labdb;
CREATE USER 'labuser'@'%' IDENTIFIED BY 'labpass123';
GRANT ALL PRIVILEGES ON labdb.* TO 'labuser'@'%';
FLUSH PRIVILEGES;
EXIT;
EOF

# Exit container
exit

Step 5: Configure Development Environment

# Access the development environment
lxc exec lab-devenv -- bash

# Install development tools
apt update
apt install -y \
    build-essential \
    git \
    vim \
    curl \
    wget \
    python3 \
    python3-pip \
    nodejs \
    npm \
    docker.io

# Install Docker Compose
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# Create a sample project structure
mkdir -p /home/developer/projects
chown -R 1000:1000 /home/developer

# Exit container
exit

Step 6: Set Up Basic Monitoring

# Access monitoring server
lxc exec lab-monitoring -- bash

# Install monitoring tools
apt update
apt install -y \
    htop \
    iotop \
    nethogs \
    tcpdump \
    nmap \
    curl \
    wget

# Install simple web-based monitoring (optional)
apt install -y python3-pip
pip3 install flask psutil

# Create a simple monitoring script
cat > /opt/simple-monitor.py << 'EOF'
#!/usr/bin/env python3
from flask import Flask, jsonify
import psutil
import subprocess

app = Flask(__name__)

@app.route('/status')
def system_status():
    return jsonify({
        'cpu_percent': psutil.cpu_percent(interval=1),
        'memory': dict(psutil.virtual_memory()._asdict()),
        'disk': dict(psutil.disk_usage('/')._asdict()),
        'network': dict(psutil.net_io_counters()._asdict())
    })

@app.route('/containers')
def container_status():
    try:
        result = subprocess.run(['lxc', 'list', '--format', 'json'], 
                              capture_output=True, text=True)
        return result.stdout
    except:
        return jsonify({'error': 'Cannot access LXC from container'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
EOF

chmod +x /opt/simple-monitor.py

# Exit container
exit

Step 7: Lab Management Scripts

Create some helper scripts for managing your lab:

# Create lab management directory
mkdir -p ~/lxd-lab

# Lab status script
cat > ~/lxd-lab/lab-status.sh << 'EOF'
#!/bin/bash
echo "=== LXD Lab Status ==="
echo
echo "Containers:"
lxc list | grep lab-
echo
echo "Network Configuration:"
lxc network show labnet | grep -E "(ipv4.address|state)"
echo
echo "Resource Usage:"
lxc list lab- -c ns4mMu
EOF

# Lab start script
cat > ~/lxd-lab/lab-start.sh << 'EOF'
#!/bin/bash
echo "Starting LXD Lab Environment..."
lxc start lab-gateway lab-webserver lab-database lab-devenv lab-monitoring
echo "Waiting for containers to start..."
sleep 10
echo "Lab is ready!"
~/lxd-lab/lab-status.sh
EOF

# Lab stop script
cat > ~/lxd-lab/lab-stop.sh << 'EOF'
#!/bin/bash
echo "Stopping LXD Lab Environment..."
lxc stop lab-gateway lab-webserver lab-database lab-devenv lab-monitoring
echo "Lab stopped."
EOF

# Make scripts executable
chmod +x ~/lxd-lab/*.sh

Step 8: Testing the Lab

# Start the lab
~/lxd-lab/lab-start.sh

# Test web server connectivity
lxc exec lab-webserver -- curl localhost

# Test database connectivity
lxc exec lab-database -- mysql -u labuser -plabpass123 -e "SHOW DATABASES;"

# Check container networking
lxc list lab-

# Test inter-container communication
lxc exec lab-devenv -- ping -c 3 lab-webserver.lxd
lxc exec lab-devenv -- curl lab-webserver.lxd

Lab Usage Examples

Accessing Services

# SSH into any container
lxc exec lab-devenv -- bash

# Forward ports to host (if needed)
lxc config device add lab-webserver web-port proxy \
    listen=tcp:0.0.0.0:8080 \
    connect=tcp:127.0.0.1:80

# Access web server from host
curl localhost:8080

Development Workflow

# Mount host directory into dev container
lxc config device add lab-devenv host-projects disk \
    source=/home/$USER/projects \
    path=/mnt/host-projects

# Work on projects inside container
lxc exec lab-devenv -- bash
cd /mnt/host-projects
# Your development work here

Backup and Restore

# Create snapshots of all lab containers
for container in lab-gateway lab-webserver lab-database lab-devenv lab-monitoring; do
    lxc snapshot $container lab-backup-$(date +%Y%m%d)
done

# Export entire lab
mkdir -p ~/lab-backups
for container in lab-gateway lab-webserver lab-database lab-devenv lab-monitoring; do
    lxc export $container ~/lab-backups/$container-$(date +%Y%m%d).tar.gz
done

Advanced Configuration

Resource Limits

# Set CPU limits
lxc config set lab-webserver limits.cpu 2
lxc config set lab-webserver limits.cpu.allowance 50%

# Set memory limits
lxc config set lab-database limits.memory 2GB
lxc config set lab-database limits.memory.swap false

# Set disk limits
lxc config device override lab-devenv root size=20GB

Security Configuration

# Enable security features
lxc config set lab-webserver security.nesting false
lxc config set lab-webserver security.privileged false
lxc config set lab-webserver security.protection.delete true

# Configure user namespaces
lxc config set lab-database raw.idmap "both 1000 1000"

Troubleshooting Common Issues

Container Won’t Start

# Check container logs
lxc info lab-webserver --show-log

# Check system resources
lxc info

# Verify storage pool
lxc storage list

Network Issues

# Check network configuration
lxc network show labnet

# Restart network
lxc network delete labnet
lxc network create labnet ipv4.address=192.168.100.1/24 ipv4.nat=true

# Check iptables rules
sudo iptables -L -n

Performance Issues

# Monitor resource usage
lxc monitor

# Check container metrics
lxc info lab-database

# Adjust limits as needed
lxc config set lab-database limits.memory 4GB

Conclusion

LXD provides a powerful platform for creating efficient, scalable container and VM environments. This lab setup demonstrates how you can quickly spin up a multi-service environment for development, testing, or learning purposes.

The combination of system containers, built-in networking, and management tools makes LXD an excellent choice for:

  • Development environments that mirror production
  • Testing infrastructure changes safely
  • Learning different Linux distributions
  • Microservices development and testing
  • CI/CD pipeline environments

With the lab setup provided, you now have a foundation to experiment with different configurations, services, and deployment scenarios. The modular nature of LXD containers makes it easy to add, remove, or modify components as your needs evolve.

Remember to regularly backup your containers and experiment with LXD’s advanced features like clustering, live migration, and custom storage backends as you become more comfortable with the platform.