So, let’s say you want to isolate some applications. And you don’t have the resources for enough HVMs such as KVM, XEN, VirtualBox, Vmware, etc. But you also don’t want to use Docker due to security concerns or because you need a “full” container that behaves like a VM, which Docker does not provide.

So, LXC containers it is! They provide something akin to a VM, while sharing resources with the host.

Please note, this guide is specifically using old school LXC, and **NOT** Canonical’s LXD!

Installation

On Debian/Ubuntu you’d simply run:

apt install lxc-utils

Now if you run ip link, you should notice a new bridge called lxcbr0!

Creating containers

The next step is to create a container, we’ll call it testing, and we’ll download an OS image (you can specify these as answers: ubuntu, jammy, amd64 or arm64):

lxc-create -n testing -t download

Now let’s check if the container is there!

lxc-ls --fancy

We can see “testing” and the state is “stopped”, so let’s start it!

lxc-start -n testing

Fixing networking (firewall)

If you run lxc-ls --fancy again, you should see the state as “running”. If you see no IPv4 and no IPv6, and you’re using ufw, we need to set up some rules:

ufw allow in on lxcbr0 to any port 67 proto udp comment 'LXC DHCP Server'
ufw allow in on lxcbr0 to any port 53 proto udp comment 'LXC DNS Server'
ufw route allow in on lxcbr0
ufw route allow out on lxcbr0

This will basically allow containers to get an IP from LXC, and also use the DNS server provided by LXC. Further, this will allow traffic forwarding to/from LXC containers (such that they can actually reach the internet).

Working with the container

OK, again, lxc-ls --fancy to see if you now have an IP, got it? Let’s continue!

lxc-attach -n testing

Now, with that command, you should be in a shell inside the “testing” container. Everything you do now, happens in the container!

Once you’re done doing your stuff, hit CTRL + D to exit the container, just as you normally would exit a shell (or enter exit).

So, let’s leave the container now, and type:

lxc-info -n testing

You should now get some basic information.

Cool, now let’s stop the container:

lxc-stop -n testing

Autostart

Now what if we want it to autostart with the system every time? Easy! First stop the container, verify it’s stopped, then run:

systemctl enable --now lxc@testing

Now verify it’s running, if it is, that means everything is working as expected!

Take good note: Now that systemd is managing our container, we no longer use lxc-start|stop -n testing and instead systemctl start|restart|stop lxc@testing!

Limit RAM

Now, let’s modify the config to limit the container memory to 2 GB of RAM:

nano /var/lib/lxc/testing/config

And add:

# Limits
lxc.cgroup.memory.limit_in_bytes = 2000000000
lxc.cgroup2.memory.max = 2000000000

Now run systemctl restart lxc@testing and now if you run free -h inside the container, you should see 2 GB of RAM!

Note: If you get any errors about one of these not existing, remove the one it claims not to exist. The difference is in whether you use cgroups v1 or v2. You might get useful information on your setup by running lxc-checkconfig!

Destroying Containers

OK, we’re done testing and don’t want the container anymore, so we run (this cannot be undone):

systemctl disable --now lxc@testing
lxc-destroy -n testing

Always make sure you really want to delete a container and that you got the name right, there is no prompt and this action cannot be undone!

Conclusion

LXC may seem daunting at first, but once you get the hang of it, it’s actually a very quick and easy way to get multiple VMs (containers, actually) running.

Please note that I run this blog in my free time. Please consider donating a cup of coffee if I helped you out! (:

Note: Before creating your first production container, I recommend reading the following article to increase the security of your setup:

Read now: How to harden the security of LXC