Skip to main content

Exploring simple Linux containers with lxc

Get started with simple containers using the tools available in the lxc project.
Jars on a shelf

If you ask sysadmins why they love Linux, one of the early answers you’ll get is flexibility. It seems that Linux tools tend to be built with just the right amount of effort having been expended for you, leaving just the right amount of work for you to do on your own. Because a sysadmin is frequently getting asked to solve problems that don’t already have an obvious solution, Linux makes for the ideal building block.

When containers became a household name, not many people knew what to do with them or what was even possible. If anything, they seemed wholly contrary to Linux’s usual philosophies. Many a sysadmin struggled with the idea of editing a YAML file and then rebuilding a container that, itself, refused to store persistent config files of its own.

Now that the dust has settled, and sysadmins are able to see containers for the infinite-scale Linux systems they are, the goal is to bring containers out of specialized industries and into common workflows. In other words, containers aren’t just for CI/CD and sysadmins any more They’re toolkits that normal users and developers can use. And it’s because it leans toward the generic that the lxc project is ideal for everyday container use.

In fact, lxc itself was the foundation that Docker was built upon, and today there are plenty of platforms that leverage the work of lxc both directly and indirectly. Lxc, unlike other container solutions, doesn’t impose a specific daemon or toolchain. Lxc is so serious about fitting into your workflow that it provides Python3 bindings so you can build tooling around it.

If you learn about lxc, you can integrate generic Linux containers into your own system design to solve whatever problem you think a container can solve.

Installing lxc

If it’s not already installed, you can install lxc with your package manager:

$ sudo dnf install lxc lxc-templates lxc-doc \
  libcgroup-pam libcgroup-tools libcgroup

Limiting privileges

Containers aren’t actual physical containers, of course, they’re just namespaces. Namespaces are meant to limit what a process “trapped” inside of a container are able to do on a system (specifically, it should only be able to do what its parent container specifies). To make sure your container infrastructure properly cripples processes that aren’t actual system users, verify that your user has a UID and GID map defined in /etc/subuid and /etc/subgid:

$ cat /etc/subuid

It’s common for a distribution to allot 65536 UIDs and GIDs to each user. Should a process happen to get outside a container launched by user seth (in this example), it would be given a UID from 100000 to 165535, so it would find itself with no permissions.

Virtual network interface

A container assumes a network is available, and most of your interactions with a container are over a network connection, even if that network is a local software-defined network interface. In order to create virtual network cards, a user must have permission to do so, and that’s not the default setting for most Linux user accounts.

If it doesn’t already exist, create the /etc/lxc/lxc-usernet file, used to set network device quotas for unprivileged users. By default, your user account isn’t allowed to create any network devices, but if you want to create and use containers you need to grant yourself the appropriate permissions. Add your user to the /etc/lxc/lxc-usernet file, along with the network device, bridge, and count:

seth veth virbr0 24

In this example, the user seth is now permitted to create up to 24 veth devices connected to the virbr0 network bridge. The veth device refers to a virtual ethernet card, and the virbr0 device is a virtual bridge. A virtual bridge is, more or less, the software equivalent of a network switch, or a Y-adapter for headphone plugs or power cables.

LXC config

Containers are defined by configuration files. If you’ve ever built a physical computer and installed Linux onto it, then you can think of a container config as the lxc version of that process. If you’ve used a Kickstart file on RHEL or Fedora, or an Ansible file on any Linux distribution, then you’ll have no trouble understanding a container config. Whether or not you have any of those experiences, you’ll be happy to know that the lxc project provides a starter config file for you to build upon.

First, create a the required local directories:

$ mkdir -p $HOME/.config/lxc
$ mkdir -p $HOME/.cache/lxc

Next, copy /etc/lxc/default.conf to $HOME/.config/lxc/default.conf:

$ cat /etc/lxc/default.conf > $HOME/.config/lxc/default.conf

Now append information about your UID and GID map to the config file. Assuming you are the first user on your host system:

$ echo "lxc.idmap = u 0 100000 65536" >> $HOME/.config/lxc/default.conf
$ echo "lxc.idmap = g 0 100000 65536" >> $HOME/.config/lxc/default.conf

Open it in a text file and make these changes: = veth = virbr0 = up = 00:16:3e:xx:xx:xx
lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536

If you’re not the first user, or if the values for UIDs and GIDs differ on your system, adjust the values according to what you have in /etc/subuid and /etc/subgid.


Reboot your system, and then log back in. Technically, you should be able to only log out, but a reboot is certain. To ensure that your user permissions have been updated.

Creating an lxc container

Once you’ve logged back in, you can create your first container using the lxc-create command. Setting the template to download prompts lxc to download a list of available base configurations, including CentOS and Fedora.

$ lxc-create --template download --name penguin

When prompted, enter your desired distribution, release, and architecture. The rootfs and image index is downloaded, and your first container is created.

Starting your container

You only have one now, but eventually, you may gather more, so if you need to list available containers on your system, use lxc-ls:

$ lxc-ls --fancy
penguin STOPPED 0         -      -    -    true 

To start a container:

lxc-start --daemon --name penguin 

You can verify that a container is running with the lxc-ls command:

$ lxc-ls --fancy

You have started the container, but you have not attached to it. Attach to it by name:

$ sudo lxc-attach --name penguin

It’s not always easy to tell when you’re in a container. A few clues are revealed by whoami, ip, and uname:

From within the container:

$ whoami
$ ip a show | grep global
    inet brd [...]
$ uname -av
Linux penguin 5.4.10-200.fc31.x86_64 #1 SMP [...]

From outside the container:

$ whoami
$ ip a show | grep global
    inet brd scope global [...]
$ uname -av
Linux fedora31 5.4.10-200.fc31.x86_64 #1 SMP [...]

You now have a container ready for development, or for use as a sandbox, or a training environment, or whatever else you want to do with your lxc sandbox.

When you’re finished, exit the container and shut it down:

# exit
$ sudo lxc-stop --name penguin


Containers have changed the way development and hosting works. They’ve made Linux the default choice for the cloud. You don’t have to change the way you work to harness their power, though. With lxc, you can create and develop containers the way that they work for you.

[ New to containers? Download the Containers Primer and learn the basics of Linux containers. ]

Topics:   Containers  
Author’s photo

Seth Kenlon

Seth Kenlon is a UNIX geek and free software enthusiast. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.