LXC Containers Installation and Configuration Guide on Ubuntu

P.S. How do I install the latest LTS version of Ubuntu (24.04) on Ubuntu 20.04 and run applications in a container sandboxed environment?

> Tested on: Ubuntu 20.04 / 24.04 (amd64)
> LXC: Classic (not LXD)


Table of Contents

1. Installing LXC
2. Creating a Container
3. Basic Management
4. Network Configuration: Local Network Access
5. SSH Access
6. Btrfs Backend via Loop File
7. Snapshots and Cloning
8. Backup and Restore
9. Useful Commands
10. Troubleshooting


Installing LXC

# Update packages and install LXC
sudo apt update
sudo apt install lxc lxc-templates btrfs-progs

# Check kernel support
lxc-checkconfig

# Check LXC network interfaces
ip ad | grep lxc

> Note: If lxc-checkconfig shows warnings, ensure the kernel has enabled: CONFIG_VETH, CONFIG_MACVLAN, CONFIG_BRIDGE, CONFIG_NET_NS.


Creating a Container

Universal Template

sudo lxc-create -t download -n <NAME> -- \
  --dist <distribution> \
  --release <version> \
  --arch <architecture>

Examples

# Ubuntu 24.04 (Noble)
sudo lxc-create -t download -n bulxc -- \
  --dist ubuntu \
  --release noble \
  --arch amd64

# Ubuntu 22.04 (Jammy)
sudo lxc-create -t download -n mylxc-ubuntu -- \
  --dist ubuntu \
  --release jammy \
  --arch amd64

| Parameter | Description |
|-----------|-------------|
| -t download | Download template for OS images |
| -n <name> | Container name |
| --dist | Distribution (ubuntu, debian, alpine) |
| --release | Release version (noble, jammy, focal) |
| --arch | Architecture (amd64, arm64) |


Basic Management

# List all containers
sudo lxc-ls

# List with details (status, IP, type)
sudo lxc-ls -f

# Start container (in background)
sudo lxc-start -n bulxc -d

# Start in foreground (for debugging)
sudo lxc-start -n bulxc -F

# Attach to container console
sudo lxc-attach -n bulxc

# Exit container
exit

# Stop container
sudo lxc-stop -n bulxc

# Force stop container
sudo lxc-stop -n bulxc --force

# Container information (status, IP, resource usage)
sudo lxc-info -n bulxc

# Delete container (must be stopped first!)
sudo lxc-stop -n bulxc 2>/dev/null
sudo lxc-destroy -n bulxc

Network Configuration: Local Network Access

By default, LXC containers use NAT networking and receive addresses like 10.0.3.x. To make the container visible on your local network (192.168.0.x), configure a bridge.

Step 1: Configure Bridge on Host (Netplan)

1. Find your physical interface name:

ip addr
   # Find the interface with your current IP, e.g., eth0 or enp3s0

2. Edit Netplan configuration:

sudo nano /etc/netplan/00-installer-config.yaml

3. Replace configuration (use spaces for indentation, critical!):

network:
     version: 2
     renderer: networkd
     ethernets:
       eth0:                    #  Your interface name
         dhcp4: no
         dhcp6: no
     bridges:
       br0:
         interfaces:
           - eth0               #  Your interface name
         dhcp4: yes             # Host gets IP via bridge
         dhcp6: no

4. Apply settings:

sudo netplan apply

5. Verify:

ip addr show br0
   # br0 should have your local IP (192.168.0.31)

Step 2: Configure Container

1. Stop the container:

sudo lxc-stop -n bulxc

2. Edit container config:

sudo nano /var/lib/lxc/bulxc/config

3. Find the network section and replace with:

lxc.net.0.type = veth
   lxc.net.0.link = br0
   lxc.net.0.flags = up
   lxc.net.0.hwaddr = 00:16:3e:xx:xx:xx

Step 3: Static IP Inside Container

1. Attach to container:

sudo lxc-attach -n bulxc

2. Edit Netplan inside (/etc/netplan/*.yaml):

network:
     version: 2
     ethernets:
       eth0:
         dhcp4: no
         addresses:
           - 192.168.0.32/24    #  Desired static IP
         routes:
           - to: default
             via: 192.168.0.1   #  Your router/gateway IP
         nameservers:
           addresses:
             - 8.8.8.8
             - 1.1.1.1

3. Apply:

netplan apply

4. Verify:

ping -c 3 8.8.8.8
   exit


SSH Access

Installing SSH Server Inside Container

# Attach to container
sudo lxc-attach -n bulxc

# Install openssh-server
apt update
apt install openssh-server -y

# Enable auto-start
systemctl enable ssh
systemctl start ssh

Allow Root Login (Optional)

> Warning: Root login via password reduces security. Use SSH keys in production.

# Inside container, edit sshd_config
nano /etc/ssh/sshd_config

Find and modify:

PermitRootLogin yes
PasswordAuthentication yes

Restart SSH:

systemctl restart ssh
exit

Connect from External Device

# From host or another device on the network
ssh root@192.168.0.32

Helpful resources:

  • SSH root setup on Ubuntu

  • Btrfs Backend via Loop File

    Enables Btrfs snapshots without a dedicated partition.

    Quick Method (Single Command)

    sudo lxc-create -t download -n bulxc5 -B loop --fssize 10G --fstype btrfs -- \
      --dist ubuntu --release noble --arch amd64

    | Parameter | Description |
    |-----------|-------------|
    | -B loop | Backend: file image as block device |
    | --fssize 10G | Size of the image file |
    | --fstype btrfs | Filesystem inside the image |

    Manual Method (Full Control)

    # 1. Create sparse file (10 GB)
    sudo fallocate -l 10G /var/lib/lxc/lxc.img
    
    # 2. Attach to loop device
    sudo losetup -f --show /var/lib/lxc/lxc.img
    # Output: /dev/loop15  remember this device
    
    # 3. Format as Btrfs
    sudo mkfs.btrfs -f /dev/loop15
    
    # 4. Mount to separate directory
    sudo mkdir -p /var/lib/lxc/btrfs
    sudo mount /dev/loop15 /var/lib/lxc/btrfs
    
    # 5. Verify
    df -T /var/lib/lxc/btrfs
    # Type should be: btrfs
    
    # 6. Create container in this directory
    sudo lxc-create -t download -n bulxc-btrfs -B btrfs -- \
      --dist ubuntu --release noble --arch amd64

    Verify Btrfs

    # Check filesystem inside container
    sudo lxc-attach -n bulxc5 -- df -T /
    
    # Check subvolumes (on host)
    sudo btrfs subvolume list /var/lib/lxc/bulxc5/rootfs

    Snapshots and Cloning

    > Only works if container was created on Btrfs (backend btrfs or loop + --fstype btrfs)

    Create Snapshot

    sudo lxc-snapshot -n bulxc5

    List Snapshots

    sudo lxc-snapshot -n bulxc5 -L

    Example output:

    snap0 (2024-03-14 10:30:15)
    snap1 (2024-03-14 12:45:22)

    Restore from Snapshot

    # Restore current container to snap0 state
    sudo lxc-snapshot -n bulxc5 -r snap0

    Clone (New Container from Snapshot)

    # Create new container "bulxc5-clone" from snap0
    sudo lxc-snapshot -n bulxc5 -r snap0 -N bulxc5-clone

    Delete Snapshot

    sudo lxc-snapshot -n bulxc5 -d snap0

    Helpful resource:
    LXC Snapshots and Clones


    Backup and Restore

    Method 1: Full Archive (tar) Universal

    #  Stop container for consistency
    sudo lxc-stop -n bulxc
    
    # Create backup
    sudo tar -czpf /home/ubuntu/lxc/bulxc-$(date +%F).tar.gz \
      -C /var/lib/lxc bulxc
    
    #  Start container again
    sudo lxc-start -n bulxc -d

    Restore:

    # Extract (container must be stopped and deleted first)
    sudo lxc-stop -n bulxc 2>/dev/null
    sudo lxc-destroy -n bulxc 2>/dev/null
    
    sudo tar -xzpf /home/ubuntu/lxc/bulxc-2024-03-09.tar.gz \
      -C /var/lib/lxc
    
    # Start
    sudo lxc-start -n bulxc -d

    Method 2: rsync for Incremental Backups

    # Stop container
    sudo lxc-stop -n bulxc
    
    # Sync with backup directory
    sudo rsync -aHAX --delete \
      /var/lib/lxc/bulxc/ \
      /backup/bulxc-live/
    
    # Start
    sudo lxc-start -n bulxc -d

    Automation: Backup Script

    Create /usr/local/bin/lxc-backup-bulxc.sh:

    #!/bin/bash
    set -e
    
    CONTAINER="bulxc"
    BACKUP_DIR="/home/ubuntu/lxc"
    DATE=$(date +%F)
    ARCHIVE="$BACKUP_DIR/${CONTAINER}-${DATE}.tar.gz"
    
    # Stop container
    lxc-stop -n $CONTAINER 2>/dev/null || true
    
    # Create archive
    tar -czpf "$ARCHIVE" -C /var/lib/lxc "$CONTAINER"
    
    # Start container
    lxc-start -n $CONTAINER -d
    
    # Clean old backups (keep last 7)
    ls -t $BACKUP_DIR/${CONTAINER}-*.tar.gz 2>/dev/null | \
      tail -n +8 | xargs -r rm
    
    echo " Backup completed: $ARCHIVE"

    Make executable and add to cron:

    sudo chmod +x /usr/local/bin/lxc-backup-bulxc.sh
    
    # Edit crontab
    sudo crontab -e
    # Add line (daily at 03:00):
    0 3 * * * /usr/local/bin/lxc-backup-bulxc.sh


    Useful Commands

    Information and Monitoring

    # Container directory size
    du -hs /var/lib/lxc/bulxc
    
    # Image file size (for loop backend)
    ls -lh /var/lib/lxc/bulxc/rootfs.disk
    
    # Resize image (loop backend)
    sudo lxc-storage-disk-resize bulxc 20G
    
    # Active loop devices
    losetup -a
    
    # Container network interfaces
    sudo lxc-attach -n bulxc -- ip addr

    Resource Management

    # Limit CPU (in container config)
    # /var/lib/lxc/bulxc/config
    lxc.cpu.cfs_quota_us = 50000
    lxc.cpu.cfs_period_us = 100000
    
    # Limit memory
    lxc.memory.limit = 1GB

    Network

    # Check container IP
    sudo lxc-attach -n bulxc -- hostname -I
    
    # Port forwarding (in container config)
    # Access container port 80 from host port 8080
    lxc.net.0.ipv4.address = 192.168.0.32/24

    Troubleshooting

    systemd fails to start: Failed to fork off sandboxing environment

    Cause: cgroups v1 (host) vs v2 (Ubuntu 24.04 container) conflict.

    Solution: Enable cgroups v2 on host:

    # 1. Edit GRUB
    sudo nano /etc/default/grub
    # Add to line:
    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash systemd.unified_cgroup_hierarchy=1"
    
    # 2. Update and reboot
    sudo update-grub
    sudo reboot
    
    # 3. Verify
    mount | grep cgroup
    # Should show: cgroup2 on /sys/fs/cgroup type cgroup2

    Alternative: Use Ubuntu 22.04 in container it's more compatible with cgroups v1.

    Container Has No Network Access

    # Check network config
    sudo nano /var/lib/lxc/bulxc/config
    # Ensure: lxc.net.0.link = br0
    
    # Check bridge on host
    ip addr show br0
    brctl show br0  # if bridge-utils installed
    
    # Check AppArmor
    sudo dmesg | grep -i apparmor
    # If needed, temporarily:
    # lxc.apparmor.profile = unconfined

    Btrfs: is not a valid backing storage type

    Cause: btrfs backend requires /var/lib/lxc to be on a Btrfs partition.

    Solution: Use loop backend with --fstype btrfs:

    sudo lxc-create -t download -n bulxc -B loop --fstype btrfs -- \
      --dist ubuntu --release noble --arch amd64

    No SSH Access

    # Inside container, check:
    systemctl status ssh
    ss -tlnp | grep :22
    ufw status  # if firewall enabled
    
    # Allow port (if needed)
    ufw allow 22/tcp

    Helpful Links

  • Official LXC Documentation

  • LXC Installation on Ubuntu (Bayrell Blog)

  • SSH root Setup

  • LXC Snapshots and Clones



LXC File Structure

/var/lib/lxc/
 <container-name>/
    config              # Container configuration
    rootfs/             # Root filesystem (or link to loop file)
    rootfs.disk         # Image file (for loop backend)
    snaps/              # Snapshots (for Btrfs)
        snap0/
        snap1/
 lxc.conf                # Global config (rarely used)

/etc/lxc/
 default.conf            # Template for new containers
 lxc-net.conf            # Network service settings

> Tip: Always test restore from backup on a test container before deleting the original. A backup without restore verification is not a backup.