Beyond Docker: Setting Up and Managing Linux Containers with LXC
Linux Containers, or LXC, is an interface for Linux kernel vritualisation. You can create Linux containers that enable persistence, and system-level functionality. When comparing LXC to Docker, they both serve different use cases. Docker is more suitable for application containerisation whereas LXC is more relevant for system-level functionality and also persistent environments.
Installing LXC
$ sudo apt update
$ sudo apt install -y lxc
Since we need to create an unprivileged container (for security reasons), the user that will be attached to this container needs to have permissions to create network devices.
$ sudo bash -c 'echo <username> veth lxcbr0 10 >> /etc/lxc/lxc-usernet'
$ cat /etc/lxc/lxc-usernet
# USERNAME TYPE BRIDGE COUNT
<username> veth lxcbr0 10
Setting up the config file
The configuration file for lxc may not exist. So create the directory inside the .config directory and copy the default.conf located in /etc/lxc/.
$ mkdir -p ~/.config/lxc
$ cp /etc/lxc/default.conf ~/.config/lxc/default.conf
$ chmod 664 ~/.config/lxc/default.conf
The configuration file has to be updated with the UID and GID of the unprivileged user. They can both be extracted from the /etc/subuid and /etc/subgid files.
$ cat /etc/subuid
ubuntu:100000:65536
<username>:165536:65536
$ cat /etc/subgid
ubuntu:100000:65536
<username>:165536:65536
$ echo lxc.idmap = u 0 165536 65536 >> ~/.config/lxc/default.conf
$ echo lxc.idmap = g 0 165536 65536 >> ~/.config/lxc/default.conf
$ cat ~/.config/lxc/default.conf
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
lxc.idmap = u 0 165536 65536
lxc.idmap = g 0 165536 65536
Setting access control list
To prevent possible permission errors, we need to set up an access control list on our .local directory.
$ sudo apt update
$ sudo apt install -y acl
$ setfacl -R -m u:165536:x ~/.local
This command sets an ACL (Access Control List) for a specific user (UID 165536), recursively on the ~/.local directory, giving that user execute (x) permission.
Creating an unprivileged container
Once the setup is complete, we can create a container using the download template. This gives us all available images designed to work without privileges.
$ lxc-create --template download --name unpriv-cont-user
Once the image index is downloaded, the CLI tool will display the images and await for the user to provide the distro, release and architecture required. In this case, ubuntu, jammy and amd64 will be used.
---
Distribution:
ubuntu
Release:
jammy
Architecture:
amd64
Downloading the image index
Downloading the rootfs
Downloading the metadata
The image cache is now ready
Unpacking the rootfs
---
You just created an Ubuntu jammy amd64 (20250404_20:34) container.
Starting the container
Now the container has been created, we can start it.
$ lxc-start -n unpriv-cont-user -d
With the container running we can interact with its environment.
$ lxc-attach -n unpriv-cont-user
# hostname
unpriv-cont-user
# exit
$ lxc-stop -n unpriv-cont-user