Run Linux containers with LXC/LXD on CentOS 8

You can download this article in PDF format via the link below to support us.Download the guide in PDF formatClose

“It’s not the blessings we call, but how we use them is the true measure of Thanksgiving.” -WT Purkiser

Demystifying LXC

The Linux Container Project (LXC) is an open source container platform that provides a set of tools, templates, libraries and language bindings. The container it provides includes a complete Linux system, like a VM, with its own file system, network, and multiple applications. LXC has a simple command-line interface that improves the user experience when starting a container (RedHat, 2020). Through powerful APIs and simple tools, it enables Linux users to easily create and manage systems or application containers. Before containerization, Docker was built on top of LXC, but since then they have moved to containerization.

Features of LXC

The current LXC uses the following kernel functions to contain processes: Source: LinuxContainers

  • Kernel namespace (ipc, uts, mount, pid, network and user)
  • Apparmor and SELinux configuration files
  • Seccomp policy
  • chroots (use pivot_root)
  • Kernel function
  • CGroups (control group)

Demystifying LXD

LXD is the next generation system container manager. It is an amazing interface for managing LXC system containers, and it should not be misunderstood as a platform or container type. The functions of LXD include snapshot and image control. As you can guess, LXD enhances the capabilities of LXC technology. It provides a user experience similar to a virtual machine, but uses Linux containers.

The function source of LXD: LinuxContainers

Some of the biggest features of LXD are:

  • Ensure security through design (unprivileged containers, resource restrictions, etc.)
  • Scalable (from containers on laptops to thousands of compute nodes)
  • Intuitive (simple, clear API and clear command line experience)
  • Image-based (various Linux distributions are released every day)
  • Support cross-host container and image transmission (including real-time migration using CRIU)
  • Advanced resource control (CPU, memory, network I/O, block I/O, disk usage and kernel resources)
  • Device pass-through (USB, GPU, Unix character and block devices, NIC, disk and path)
  • Network management (network bridge creation and configuration, cross-host tunneling, etc.)
  • Storage management (supports multiple storage backends, storage pools and storage volumes)

Install LXC/LXD on CentOS 8

If you want to try LXC/LXD on CentOS 8 server to run certain applications, the following steps will help you get the platform ready for use as soon as possible.

Step 1: Update and prepare the server

This is a very critical step. We ensure that our house is well furnished by ensuring that the latest patches and software packages are installed. Continue to run the following commands to prepare the server.

sudo dnf update -y && sudo dnf upgrade -y
sudo dnf install -y vim curl nano 

Disable SELinux

If you are good at managing SELinux context, then this is an optional step.To make it lenient, run the following command

sudo setenforce 0
sudo sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config

Step 2: Enable and configure EPEL repository

Run the following commands to install and enable EPEL repo on CentOS 8, then update the server to get the latest packages from Epel.

sudo yum install
sudo dnf update

Step 3: Install the snapshot on CentOS 8

In this setup, we will install Snappy’s LXD package because it has the simplicity and support of the Snap package. Therefore, we need to install snapd on the server as follows:

sudo yum install snapd -y

After installation, you need to enable the systemd unit for managing the main snapshot communication socket:

sudo systemctl enable --now snapd.socket

To enable classic snapshot support, enter the following to create a symbolic link between /var/lib/snapd/snap and /snap:

sudo ln -s /var/lib/snapd/snap /snap

Log out and log in again, or restart the system to ensure that the snapshot path is updated correctly. Once the snapshot is installed, let’s move on to the next step.

Step 4: Add kernel parameters

LXD requires some important kernel options, which we will enable on the server. Configure them by running the following commands on the terminal: root.

$ sudo su -

# grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
# grubby --args="namespace.unpriv_enable=1" --update-kernel="$(grubby --default-kernel)"
# echo "user.max_user_namespaces=3883" | sudo tee -a /etc/sysctl.d/99-userns.conf

After configuring these settings, the server needs to be rebooted because the core core functionality has changed. Restart the server.

sudo reboot

Step 5: Install lxd snap on CentOS 8

Finally, after the server is backed up, it’s time to retrieve the LXD package of interest from the Snap storage. It’s as simple as making a Snap, we just need to run the following command, and our LXD will be installed.

$ sudo snap install --classic lxd

Step 6: Start the test LXD container

So far, we have installed LXC/LXD, but there is no container that can hold the application we are interested in. Therefore, before starting some containers, let’s add the user account to the group lxd so that it can manage the LXD container without permission restrictions.

sudo usermod -aG lxd <your-username>
newgrp lxd

note: of newgrp The command is used to change the current group ID during the login session. If the optional-flag is given, the user’s environment will be reinitialized as if the user has logged in, otherwise the current environment (including the current working directory) remains unchanged. newgrp changes the current real group ID to a named group.

Next, we configure or “initialize” the LXD environment by running the following commands. It will take you to solve several problems. Please answer according to your environmental needs. I use the default value for the blank value.

$ lxd init

Would you like to use LXD clustering? (yes/no) [default=no]:
Do you want to configure a new storage pool? (yes/no) [default=yes]:
Name of the new storage pool [default=default]:
Name of the storage backend to use (btrfs, dir, lvm, ceph) [default=btrfs]: lvm
Create a new LVM pool? (yes/no) [default=yes]:
Would you like to use an existing empty block device (e.g. a disk or partition)? (yes/no) [default=no]:
Size in GB of the new loop device (1GB minimum) [default=9GB]: 5GB
Would you like to connect to a MAAS server? (yes/no) [default=no]:
Would you like to create a new local network bridge? (yes/no) [default=yes]:
What should the new bridge be called? [default=lxdbr0]:
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
Would you like LXD to be available over the network? (yes/no) [default=no]:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:

The above command will create a bridge lxdbr0. We add this bridge interface to the trusted zone so that the connection can pass.In other words, we will allow all incoming traffic to pass lxdbr0.Execute the following firewall commands

sudo firewall-cmd --add-interface=lxdbr0 --zone=trusted --permanent
sudo firewall-cmd --reload

Create a container

After lxd is initialized, your user will be granted permission to start and manage containers with the following permissions: x Command, let’s create a container. The following syntax can be used as a guide:

lxc launch images:[distro]/[version]/[architecture] [your-container-name]

Now that we are enlightened enough and effortlessly, let’s create a test CentOS 8 and Ubuntu 20.04 container by running the following command:

$ lxc launch images:centos/8/amd64 cent8

Creating cent8
Retrieving image: Unpack: 100% (4.22GB/s)
Starting cent8

Start the Ubuntu container by running the following command:

$ lxc launch ubuntu:20.04 ubuntu20

Creating ubuntu20
Starting ubuntu20

Once they are started, you can easily list your containers, thereby:

$ lxc list

| NAME  |  STATE  |        IPV4         |                     IPV6                      |   TYPE    | SNAPSHOTS |
| cent8 | RUNNING | (eth0) | fd42:b3a2:efa8:5aa5:216:3eff:fe1d:38c3 (eth0) | CONTAINER | 0         |

You can also stop, start, restart, delete and check more information about the container, as shown below, whereIs the name of the container, as shown in the lxc list command.

lxc start <container>
lxc stop <container>
lxc restart <container>
lxc delete <container>


lxc stop ubuntu20
lxc delete ubuntu20

Note that the running container must be stopped before it can be deleted.

Use the info command option to get information about the container

$ lxc info container

##For example
$ lxc info cent8

Example excellent output:

Name: cent8
Location: none
Remote: unix://
Architecture: x86_64
Created: 2020/11/07 11:25 UTC
Status: Running
Type: container
Profiles: default
Pid: 2724
  eth0: inet    veth975e84ff
  eth0: inet6   fd42:b3a2:efa8:5aa5:216:3eff:fe1d:38c3  veth975e84ff
  eth0: inet6   fe80::216:3eff:fe1d:38c3        veth975e84ff        
  lo:   inet
  lo:   inet6   ::1
  Processes: 13
  Disk usage:
    root: 737.98MB
  CPU usage:
    CPU usage (in seconds): 1
  Memory usage:
    Memory (current): 93.32MB
    Memory (peak): 98.56MB
  Network usage:
      Bytes received: 3.57kB
      Bytes sent: 2.22kB
      Packets received: 30
      Packets sent: 22
      Bytes received: 0B
      Bytes sent: 0B
      Packets received: 0
      Packets sent: 0

Step 7: Execute temporary commands in the container:

Like you can “carried outIn a Docker container, you can also run commands in an lxd container. The syntax is like this.

$ lxc exec <container-name> <command>

Examples of executing commands are as follows:

$ lxc exec cent8 -- yum -y update

CentOS-8 - AppStream                                                                                  538 kB/s | 5.8 MB     00:11    
CentOS-8 - Base                                                                                       619 kB/s | 2.2 MB     00:03    
CentOS-8 - Extras                                                                                     8.1 kB/s | 8.1 kB     00:01    
Dependencies resolved.
Nothing to do.

Let’s install Apache in the container

$ lxc exec cent8 -- yum -y install httpd

Last metadata expiration check: 0:00:41 ago on Sat Nov  7 12:56:38 2020.
Dependencies resolved.
====================================================================================================================================== Package                         Architecture        Version                                             Repository              Size 
 httpd                           x86_64              2.4.37-21.module_el8.2.0+494+1df74eae               AppStream              1.7 M 
Installing dependencies:
 apr                             x86_64              1.6.3-9.el8                                         AppStream              125 k 
 apr-util                        x86_64              1.6.1-6.el8                                         AppStream              105 k

After installation, we can log in to the container, create a sample page, start the web server and check its status

$ lxc exec cent8 -- /bin/bash

##We are now in the container
[[email protected] ~]# systemctl start httpd
[[email protected] ~]# systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: active (running) since Sat 2020-11-07 12:58:09 UTC; 5s ago
     Docs: man:httpd.service(8)
 Main PID: 175 (httpd)
   Status: "Started, listening on: port 80"
    Tasks: 213 (limit: 11069)
   Memory: 27.6M
   CGroup: /system.slice/httpd.service
           ├─175 /usr/sbin/httpd -DFOREGROUND
           ├─176 /usr/sbin/httpd -DFOREGROUND
           ├─177 /usr/sbin/httpd -DFOREGROUND
           ├─178 /usr/sbin/httpd -DFOREGROUND
           └─179 /usr/sbin/httpd -DFOREGROUND

Create a sample page in the container for Apache to demonstrate

[[email protected] ~]# vi /var/www/html/index.html

<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <LINK href="" rel="stylesheet" type="text/css">


<img src="forkit.gif" id="octocat" alt="" />
<h2> About SELinux </h2><br>
  SELinux gives you the ability to limit the privileges associated with executing processes and reduce the damage that could result from system and applications vulnerabilities exploitation. For this reason, it is recommended to keep SELinux in enforcing mode unless you have a good reason to disable it.
<h2> Modes</h2><br>
  The other available mode for running SELinux in enabled state is Permissive. In this mode, SELinux policy is not enforced and access is not denied but denials are logged for actions that would have been denied if running in enforcing mode.


Then restart Apache in the container and exit.

[[email protected] ~]# systemctl restart httpd

Step 8: Access the application in the container from the outside

Well, now that you have deployed your application on a given container (for example, Apache in the above command), how will the target audience access your hosted content from the outside? You can use firewall rules, or you can use a more elegant approach, you can deploy a reverse proxy to route traffic to your application.

Use a reverse proxy server, such as Nginx

Install Nginx web server on CentOS 8 host system:

sudo yum -y install vim nginx

Set up the Nginx HTTP proxy for the service

Create a new configuration file.

sudo nano /etc/nginx/conf.d/app1.conf

Modify this configuration code snippet to suit your setup. Note that Nginx will listen on port 9090 and then redirect traffic to the container where Apache is running on port 80.

##App1 Upstreams

upstream app1 {
 server;  ##Notice the IP of the container here.

server {
    listen 9090;
    access_log /var/log/nginx/app1_access.log;
    error_log /var/log/nginx/app1_error.log;

    # Proxy settings
    proxy_read_timeout 720s;
    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;

    # Request for root domain
    location / {
       proxy_redirect off;
       proxy_pass http://app1;

    # Gzip
    gzip_types text/css text/less text/plain text/xml application/xml application/json application/javascript;
    gzip on;

A valid DNS record is necessary for external (public) access to your application.

Check your configuration syntax:

$ sudo nginx  -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If the setting returns positive feedback, please restart the nginx service.

sudo systemctl restart nginx

Allow port 9090 on the firewall

sudo firewall-cmd --permanent --add-port=9090/tcp
sudo firewall-cmd --reload

Now, we are ready to access our application. Open your favorite browser and point it to the IP address and port of the FQDN or Nginx proxy we just completed. http: // : 9090. You should see a page similar to the following.

LXC Apache application

in conclusion

We finally managed to install, manage and manage lxd / lxc Containers and hosting a simple application in one of the containers. We hope that this guide will provide you with as much information as you expect, and everything will be effective for you. Meeting you on the blog is enough to make us thank you for your visit and crazy support. Check out other exquisite guides below.

How to deploy LXD on CentOS 7 using Snap

Use Podman and Libpod to run Docker containers

How to run Docker/Podman containers as system services

You can download this article in PDF format via the link below to support us.Download the guide in PDF formatClose