In this chapter, we’ll learn how to use Podman and Red Hat Universal Base Image, also called UBI. Together, Podman and UBI provide users with the software they need to run, build, and share enterprise-quality containers on Red Hat Enterprise Linux (RHEL).
In recent years, being able to understand and use containers has become a key requirement for Red Hat system administrators. In this chapter, we’ll review the basics of containers, how containers work, and the standard tasks for managing containers.
You will also learn how to run containers with simple commands, build enterprise-quality container images, and deploy them on a production system. Finally, you will learn when to use more advanced tools such as Buildah and Skopeo.
In this chapter, we will cover the following topics:
In this chapter, we will review the basic usage of Podman, Buildah, and Skopeo, as well as how to build and run containers using Red Hat UBI.
We will create and run containers on the local RHEL 9 system, which we deployed in Chapter 1, Getting RHEL Up and Running. You will need to have the container-tools package from the Application Stream repository installed.
Containers provide users with a new way to run software on Linux systems. Containers provide all the dependencies related to a given piece of software, in a consistent and redistributable manner. Containers were first made popular by Docker, Google, and Red Hat, and many others joined Docker to create a set of open standards called the Open Container Initiative (OCI). The popularity of the OCI standards has facilitated a large ecosystem of tools where users don’t have to worry about compatibility between popular container images, registries, and tools. Containers have become standardized in recent years and most major tools follow three standards governed by the OCI, outlined here:
You can learn more about this at https://opencontainers.org/.
All container tools (Docker, Podman, Kubernetes, and so on) need an operating system to run the container, and each operating system can choose different sets of technology to secure containers, so long as they comply with the OCI standards. RHEL uses the following operating system capabilities to securely store and run containers:
Many systems administrators use VMs to isolate applications and their dependencies (libraries and so on). Containers provide the same level of isolation but reduce the overhead of virtualization. Since containers are simple processes, they do not need a virtual CPU (vCPU) with all of the overhead of translation. Containers are also smaller than VMs, which simplifies management and automation. This is particularly useful for continuous integration/continuous delivery (CI/CD).
RHEL provides users with container tools and images that are compatible with all OCI standards. This means they work in a way that is very familiar to anyone who has used Docker. For those unfamiliar with these tools and images, the following concepts are important:
The container tools provided with RHEL make it easy to deploy containers at a small scale, even for production workloads. But to manage containers at scale and with reliability, container orchestration such as Kubernetes is a much better fit. Red Hat, following the lessons learned from building Linux distributions, has created a Kubernetes distribution called OpenShift. If you need to deploy containers at scale, we recommend you take a look at this platform. The container tools and images provided in RHEL, and introduced in this chapter, will provide a solid foundation for deploying to Kubernetes/OpenShift if and when you are ready for it. The tools introduced in this chapter have been built in a way that will prepare your applications to be deployed in Kubernetes when you are ready for it.
To manage pods, containers, and container images, we can use Podman; to create OCI images, we can use Buildah; and to inspect container images and repositories on registries, we can use Skopeo. So, let’s install them:
[root@rhel-instance ~]# dnf install podman buildah skopeo –y
[omitted]
Let’s take a look at the main tools that we have installed, as follows:
You now have a machine installed with all of the tools you will need to build, run, and manage containers on an RHEL 9 system.
Now that you have the container tools from the Application Stream repository installed, let’s run a simple container based on Red Hat UBI that contains a set of official container images and extra software based on RHEL. To run a UBI image, it only takes a single command, as illustrated in the following code snippet:
[root@rhel-instance ~]# podman run –it registry.access.redhat.com/ubi9/ubi bash
Trying to pull registry.access.redhat.com/ubi9:latest...
Getting image source signatures
Checking if image destination supports signatures
Copying blob bf30f05a2532 done
Copying blob c6e5292cfd5f done
Copying config 168c58a383 done
Writing manifest to image destination
Storing signatures
[root@e38453f5e055 /]#
Check the hostname; it has changed from rhel-instance to e38453f5e055, which means we are inside the container.
Tip
These tutorials run commands as root, but one of the benefits of Podman is that it can run containers as a regular user without special permissions or a running daemon in the system.
You now have a fully isolated environment to execute whatever you want. You can run any commands you’d like in this container. It’s isolated from the host and from other containers that might be running, and you can even install software on it.
Note
Red Hat UBI is based on software and packages from RHEL. This is the official image to use with RHEL and provides a rock solid, enterprise-ready base for your containers. UBI will be used throughout this chapter.
Running a one-off container such as this is useful for testing new configuration changes and new pieces of software without interfering with the software directly on the host.
Note
Red Hat UBI contains a minimal set of tools for this image to be used as an enterprise-ready base. If you want to run common commands that have not been installed in the system such as ps, you need to install them first using dnf, like a normal system.
Let’s install the package that contains the ps command:
[root@e38453f5e055 /]# dnf install procps-ng
Updating Subscription Management repositories.
Unable to read consumer identity
Subscription Manager is operating in container mode.
[output omitted]
[output omitted]
Installed:
procps-ng-3.3.174.el9.x86_64
Complete!
Let’s take a look at the processes running in the container, as follows:
[root@e38453f5e055 /]# ps -efa
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 21:13 pts/0 00:00:00 bash
root 39 1 0 21:17 pts/0 00:00:00 ps -efa
As you can see, the only processes that are running are the shell we are using and the command we have just run. It is a completely isolated environment.
Now, exit the container by running the following command:
[root@e38453f5e055 /]# exit
[root@rhel-instance ~]#
Now that we have a working set of container tools and a UBI container image cached locally, we’re going to move on to some more basic commands.
In this section, we’ll run some basic commands to get familiar with using containers. First, let’s pull some more images, as follows:
[root@rhel-instance ~]# podman pull registry.access.redhat.com/ubi9/ubi-minimal
...
[root@rhel-instance ~]# podman pull registry.access.redhat.com/ubi9/ubi-micro
...
[root@rhel-instance ~]# podman pull registry.access.redhat.com/ubi9/ubi-init
...
We now have several different images cached locally. Let’s take a look at these here:
[root@rhel-instance ~]# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.access.redhat.com/ubi9/ubi-micro latest 8bbc013a1407 13 days ago 26.2 MB
registry.access.redhat.com/ubi9/ubi-init latest a991566cfe2c 13 days ago 244 MB
registry.access.redhat.com/ubi9 latest 168c58a38365 2 weeks ago 228 MB
registry.access.redhat.com/ubi9/ubi-minimal latest 4a8128b051b8 2 weeks ago 129 MB
Notice that we have four images cached locally. Red Hat UBI comes in multiple flavors, as outlined here:
Now that you understand the basics of the four types of base images, let’s start a container in the background so that we can inspect it while it’s running. Start it in the background with the following command:
[root@rhel-instance ~]# podman run -itd --name background ubi9 bash
b75aeb784f71d990f4a6413acc1e09046ad966fce6df5e7232517f8ad642 d4ae
Notice that when we start the container, the shell returns to normal and we can’t type commands in the container. Our Terminal doesn’t enter a shell in the container. The -d option specified that the container should run in the background. This is how most server-based software, such as web servers, runs on a Linux system.
We can still connect our shell to a container running in the background if we need to troubleshoot one, but we have to determine which container we want to connect to. To do this, list all of the containers that are running with the following command:
[root@rhel-instance ~]# podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b75aeb784f71 registry.access.redhat.com/ubi9:latest bash 18 seconds ago Up 18 seconds ago background
We could reference the container using the CONTAINER ID value, but we have started the container with the name background to make it easier to reference. We can enter the container and see what is going on inside it with the exec subcommand, as follows:
[root@rhel-instance ~]# podman exec –it background bash
[root@b75aeb784f71 /]#
After you type a few commands, exit the container by running the following command:
Now, let’s stop the containerized process by running the following command:
[root@rhel-instance /]# podman stop background
Check it’s stopped by running the following command:
[root@rhel-instance ~]# podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b75aeb784f71 registry.access.redhat.com/ubi9:latest bash 2 minutes ago Exited (137) 33 seconds ago background
Notice that the state is Exited. This means the process has been stopped and is no longer in memory, but the storage is still available on disk. The container can be restarted, or we can delete it permanently with the following command:
[root@rhel-instance ~]# podman rm background
b75aeb784f71d990f4a6413acc1e09046ad966fce6df5e7232517f8ad642 d4ae
This deleted the storage, which means the container is gone forever; you can remove all the stopped containers as a cleanup step. Verify that no containers are left by running the following command:
[root@rhel-instance ~]# podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
This section taught you some basic commands. Now, let’s move on to attaching storage.
Remember that the storage in a container is ephemeral. Once the podman rm command is executed, the storage is deleted. If you have data that you need to save after the container has been removed, you need to use a volume. To run a container with a volume, execute the following command:
[root@rhel-instance ~]# podman run –it --rm -v /mnt:/mnt:Z --name data ubi9 bash
[root@228b44c14e2f /]#
The preceding command has mounted /mnt into the container, and the Z option has told it to appropriately change the SELinux labels so that data can be written to it. The --rm option ensures that the container is removed as soon as you exit the shell. Now, you can save data on this volume, and it won’t be removed when you exit the container. Add some data by running the following command:
[root@12ad2c1fcdc2 /]# touch /mnt/test.txt
[root@12ad2c1fcdc2 /]# exit
exit
[root@rhel-instance ~]#
Now, inspect the test file you created by running the following command:
[root@rhel-instance ~]# ls /mnt/
test.txt
Notice that the file is still on the system, even though the container has been removed and its internal storage has been deleted.
Since Podman is not a daemon, it relies on systemd to start a container when the system boots. Podman makes it easy to start a container with systemd by creating a systemd unit file for you. The process of running a container with systemd looks like this:
First, let’s run an example container, as follows:
[root@rhel-instance ~]# podman run -itd --name systemd-test ubi9 bash
ba3a30f45379b7cb5006844ebbd4e626962055aad8784b8288b199261d f92b1f
Now, let’s export the systemd unit file that we’ll use to start this container, as follows:
[root@rhel-instance ~]# podman generate systemd --name --new systemd-test > /etc/systemd/system/podman-test.service
Enable and start the service by running the following command:
[root@rhel-instance ~]# systemctl daemon-reload
[root@rhel-instance ~]# systemctl enable --now podman-test
Created symlink /etc/systemd/system/default.target.wants/podman-test.service → /etc/systemd/system/podman-test.service.
Test that the container is running by executing the following command:
[root@rhel-instance ~]# systemctl status podman-test
podman-test.service - Podman container-systemd-test.service
Loaded: loaded (/etc/systemd/system/podman-test.service; enabled; vendor preset: disabled)
Active: active (running) since Thu 2022-08-17 21:29:30 EDT; 13min ago
[output omitted]
...
Now, check that the container is running by using the podman command, as follows:
[root@rhel-instance ~]# podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
12f8c495f20c registry.access.redhat.com/ubi9:latest bash About a minute ago Up About a minute ago systemd-test
This container will now start every time the system boots; even if you kill the container with Podman, systemd will always make sure this container is running. Podman and systemd make it easy to run containers in production. Now, let’s stop the container with systemctl and disable it, as follows:
[root@rhel-instance ~]# systemctl stop podman-test
[root@rhel-instance ~]# systemctl disable podman-test
Now that we know how to run containers, let’s learn how to build container images. Container images are commonly built with a file that serves as a blueprint for how to build them the same way every time. A Dockerfile or Containerfile contains all of the information necessary to build container images. It makes it easy to script how a container will get built. A Containerfile is just like a Dockerfile, but its name attempts to make it more agnostic and not tied to the Docker tooling. Either type of file can be used with the container tools that come with RHEL. Start by creating a file called Containerfile inside a test folder with the following content:
FROM registry.access.redhat.com/ubi9/ubi RUN yum update -y
This simple Containerfile pulls the UBI standard base image and applies all of the latest updates to it. Now, let’s build a container image by running the following command:
[root@rhel-instance ~]# mkdir test
[root@rhel-instance ~]# mv Containerfile test/
[root@rhel-instance ~]# podman build –t test-build ./test
STEP 1: FROM registry.access.redhat.com/ubi9/ubi
You now have a new image called test-build that has a new layer containing all of the updated packages from the Red Hat UBI repositories, as illustrated in the following code snippet:
[root@rhel-instance ~]# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/test-build latest 98abd9c1754a 9 minutes ago 260 MB
... [output omitted] ...
The workflow for building images from a Dockerfile or Containerfile is nearly identical to how Docker was in RHEL 7 or any other operating system. This makes it easy for system administrators and developers to move to Podman.
Container registries are like file servers for container images. They allow users to build and share container images, resulting in better collaboration. Often, it’s useful to pull container images from public registry servers that are located on the internet, but in many instances, corporations have private registries that are not public. Podman makes it easy to search multiple registries, including private registries, on your company’s network.
Podman comes with a configuration file that allows users and administrators to select which registries are searched by default. This makes it easy for users to find the container images that administrators want them to find.
A set of default registries to search for are defined in /etc/containers/registries.conf. Let’s take a quick look at this file by filtering all the comments in it, as follows:
[root@rhel-instance ~]# cat /etc/containers/registries.conf | grep -v ^#
unqualified-search-registries = ["registry.access.redhat.com", "registry.redhat.io", "docker.io"]
short-name-mode = "enforcing"
As you can see, we have the unqualified-search-registries variable for secure registries, which includes the two main Red Hat registries, registry.access.redhat.com and registry.redhat.io, as well as the docker.io Docker registry. All of these registries are secured with Transport Layer Security (TLS) certificates, but Podman can also be configured to pull images without encryption using the registries.insecure section.
Separately from TLS, all images provided by Red Hat are signed and provide a signature store that can be used to verify them. This is not configured by default and is beyond the scope of this chapter.
The short-name-mode option supports three modes to control the behavior of short-name resolution:
By default, short-name-mode is configured with enforcing.
To verify that Podman is using and searching the proper registries, run the following command:
[root@rhel-instance ~]# podman info | grep registries -A 4
registries:
search:
- registry.access.redhat.com
- registry.redhat.io
- docker.io
Tip
If you want to publish your own images, you can do this in the service that Red Hat offers to do so: https://quay.io. You can also configure registries.conf to search quay.io for images you store there.
Let’s review the options that we used with Podman in this chapter:
Table 17.1 – List of Podman options
As you can see, Podman includes options for managing the full container life cycle. Most Podman commands are compatible with docker. Podman even provides a package (podman-docker) that supplies an alias from podman to docker so that users can continue to type a command they are familiar with. While Podman and Docker feel quite similar to use, Podman can be run as a regular user and does not require a daemon to be continuously running. Let’s move on to the next section to explore some advanced use cases.
Podman is a general-purpose container tool and should solve 95% of a user’s needs. Podman leverages Buildah and Skopeo as libraries and pulls these tools together under one interface. That said, there are edge cases where a user may want to leverage Buildah or Skopeo separately. We will explore two such use cases here.
Building from a Dockerfile or Containerfile is quite easy, but it does come with some trade-offs. For example, Buildah is good in the following situations:
For this example, let’s build on top of UBI Micro to demonstrate why Buildah is such a great tool. First, create a new container to work with, as follows:
[root@rhel-instance ~]# buildah from registry.access.redhat.com/ubi9/ubi-micro
ubi-micro-working-container
The preceding command created a reference to a new container called ubi-micro-working-container. Once Buildah creates this reference, you can build upon it. To make this easier, let’s start over and save the reference in a shell variable, as follows:
[root@rhel-instance ~]# microcontainer=$(buildah from registry.access.redhat.com/ubi9/ubi-micro)
Now, you can mount the new container as a volume. This lets you modify the container image by changing the files in a directory. Run the following command to do this:
[root@rhel-instance ~]# micromount=$(buildah mount $microcontainer)
Once the container storage has been mounted, you can modify it in any way you like. These changes will eventually be saved as a new layer in the container image. This is where you could run an installer (install.sh), but in the following example, we will use the package manager on the host to install packages in UBI Micro:
[root@rhel-instance ~]# yum install
--installroot $micromount --releasever 9 --setopt install_weak_deps=false --nodocs -y httpd
... [output omitted] ...
[root@rhel-instance ~]# yum clean all
... [output omitted] ...
When the package installation completes, we will unmount the storage and commit the new image layer as a new container image called ubi-micro-httpd, as illustrated in the following code snippet:
[root@rhel-instance ~]# buildah umount $microcontainer
232e2cdde4171c465f63b013214e5f1a161a1e62969e265c92487c92ce1f 73b9
[root@rhel-instance ~]# buildah commit $microcontainer ubi-micro-httpd
Getting image source signatures Copying blob 5f70bf18a086 skipped: already exists
Copying blob a5198d17d541 skipped: already exists
Copying blob 80a8684cd0a5 done
Copying config c857c77f14 done
Writing manifest to image destination
Storing signatures
c857c77f14674d79114010d80031b6fc5ae309f524d0ef10bf2f1d242e3 41202
You now have a new container image with httpd installed, built on UBI Micro. Only a minimal set of dependencies have been pulled in. Look at how small the image is:
[root@rhel-instance ~]# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/ubi-micro-httpd latest c857c7 7f1467 About a minute ago 26.3 MB
Buildah is a wonderful tool that gives you a lot of control over how builds are done. Now, we will move on to Skopeo.
Skopeo is specifically designed and built to work on remote container repositories. With the following command, you can easily remotely inspect the available tags for an image:
[root@rhel-instance ~]# skopeo inspect docker://registry.access.redhat.com/ubi9/ubi
{
"Name": "registry.access.redhat.com/ubi9/ubi",
"Digest": "sha256:aee6d39282dabc3374a01d4a81f97c6827cbcdcf155cadb5a42 966134205b05d",
"RepoTags": [
"9.0.0-1468",
"9.0.0-1468.1655190709",
... [output omitted] ...
Remote inspection is useful for determining whether you want to pull an image and, if so, with which tag. Skopeo can also be used to copy between two remote registry servers without caching a copy in the local storage. For more information, see the skopeo man pages.
In this chapter, we reviewed the basics of how to run, build, and share containers on RHEL 9. With this, you are prepared to create containers, run them, manage them, and even use systemd to ensure they’re always running in a production environment.
You are now ready to leverage the functionality and ease of deployment that containers provide. While a deep dive into all of the intricacies of migrating software into containers is outside the scope of this book, containers simplify the packaging and delivery of applications ready to be executed with all of their dependencies.
Containers are now a strong focus within the IT industry. Containers alone simplify how applications are packaged and delivered, but orchestration platforms such as OpenShift (based on Kubernetes) make it easier to deploy, upgrade, and manage containerized applications at scale.
Congratulations – you have made it to the end of this chapter! Now, it’s time to move on to the next chapter and take a self-assessment to ensure you’ve absorbed the material; this will help you practice your skills. There are two more chapters to go.