Running containers with Podman
Foreword
It is much easier to put existing resources to better use, than to develop resources where they do not exist.
— George Soros
It is no surprise that application containerization has brought a paradigm shift in software development and operation, offering numerous advantages from both development and operational standpoints.
By encapsulating applications and their dependencies into lightweight, portable containers, developers experience higher level of productivity, scalability, and consistency throughout the development lifecycle.
Simultaneously, operations teams benefit from increased efficiency, flexibility, and optimal resource utilization. This transformative technology has ushered in a new era of software deployment, simplifying the process of building, packaging, and deploying applications across diverse environments.
In this article, I would like to take you on a journey to learn, understand and deploy container technology using Podman, a popular container management tool.
Containers
Before we dive deep into containers it is worthwhile to compare and contrast it with something familiar such as virtualization.
Virtualization and containerization are both technologies used for software deployment and isolation, but they have key similarities and differences.
Similarities include:
- Isolation: Both virtualization and containerization provide a level of isolation for applications and their dependencies, preventing conflicts and interference between different software components.
- Resource Allocation: Both technologies allow for efficient allocation and utilization of system resources such as CPU, memory, and storage.
- Deployment Flexibility: Virtualization and containerization enable the deployment of applications across different environments and platforms, providing portability and flexibility.
Differences include:
- Architecture: Virtualization creates a virtual machine (VM) that emulates an entire operating system (OS) with its own kernel and resources, allowing the execution of multiple OS instances on a single physical server. Containerization, on the other hand, uses the host OS kernel and abstracts applications into lightweight, isolated containers without the need for a separate OS.
- Overhead: Virtualization introduces a higher overhead due to the need for a complete OS emulation, including the hypervisor layer. Containerization has a lower overhead as it leverages the host OS, resulting in faster startup times and more efficient resource usage.
- Density: Virtualization allows for running multiple instances of different OSes, making it suitable for scenarios requiring full OS isolation. Containerization, with its lightweight nature, can achieve higher density, running multiple containers on a single OS instance.
- Portability: Containers are highly portable and can be easily moved between environments, as long as the target system supports the container runtime. Virtual machines, however, may require specific configurations or adjustments when moving between different virtualization platforms.
- Security: Virtualization provides strong isolation between VMs, making it suitable for scenarios where stronger isolation is required. Containers share the host OS kernel, which introduces potential vulnerabilities if not properly configured. However, containerization technologies have improved security features such as user namespaces and resource limitations to mitigate these risks.
💡Tip: Although running containers does not require any underlying hypervisor on the host machine it is common to run containers inside a virtual machine for additional flexibility.
Now that we are more familiar with containerization and the benefits it bring lets have a look how we can implementing it using Podman.
Podman
Podman is an open-source container runtime management tool that has gained popularity as an alternative to Docker. It originates from the broader container ecosystem in the Linux world and provides a lightweight, secure, and efficient environment for managing containers.
Podman addresses several key problems faced by developers and administrators. Firstly, it allows users to run containers without requiring a daemon (system service), eliminating the need for a root process, enhancing security, and providing a more streamlined experience.
Additionally, it offers a familiar command-line interface, allowing users to easily transition from Docker and leverage existing container management knowledge. Furthermore, it provides improved compatibility with the Open Container Initiative (OCI) standards, enabling better interoperability with other container tools and platforms.
Podman leverages existing Linux kernel features to create and manage container instances. These features include:
- Namespaces provide process isolation by creating separate instances of global system resources. They allow multiple processes to have their own isolated view of various system resources, such as process IDs, network interfaces, file systems, and more. Each namespace provides a virtual environment, enabling processes to operate independently within their designated namespace while sharing the underlying kernel and hardware.
- Control Groups (cgroups) enable resource allocation and resource limiting for processes and groups of processes. Cgroups allow fine-grained control over system resources such as CPU, memory, disk I/O, and network bandwidth. They provide a way to prioritize and manage resource usage.
- SELinux (Security-Enhanced Linux) provides fine-grained access control and mandatory access control (MAC) policies. It can enhance the security of containerized applications by enforcing access controls and limiting the scope of potential security breaches. It helps to isolate and protect containers from potential attacks and provides an additional layer of defense in a containerized infrastructure.
- Seccomp (Secure Computing Mode) provides a sandboxing mechanism for restricting the system calls that a process can make. It allows fine-grained control over the system calls that containerized processes are allowed to execute, reducing the attack surface and mitigating potential security risks.
For complete solution Podman can also interacts with other tools such as:
- Buildah provides finer-grained control for building OCI compliant container images
- Skopeo to inspect images and provide additional capabilities when interacting with image registries.
💡Tip: Podman has capability to build container images as well as interact with registries directly. The above tools are specialized and can be leveraged for advanced scenarios, for example when you do not need container runtime capabilities.
Finally, I would like to highlight the difference between the container image, container instance and container runtime.
- Container image is a lightweight, standalone, and executable package (tar archive) that contains metadata, dependencies and configurations needed to run a piece of software. It is a static snapshot of an application, along with its runtime environment, libraries, and any other necessary files.
- Container instance, on the other hand, refers to a running instance of a container based on a container image. It is an isolated and lightweight execution environment that runs the software contained within the image. Multiple container instances can be created from the same container image, and each instance operates independently with its own isolated resources and runtime environment.
- Container runtime is a software component responsible for executing and managing containerized applications. It provides the necessary infrastructure to create, start, stop, and monitor containers on a host system. The container runtime interacts with the underlying operating system and hardware to isolate and allocate resources for containers, enabling them to run in a separate and isolated environment. It also handles tasks such as networking, storage, security, and lifecycle management of containers. Popular container runtimes include crun, Docker, runc, containerd, and CRI-O.
In the next section, we are going to install and configure Podman in order to run a container image as systemd service.
Workflow
Software Installation
Our starting point for this demonstration will be a Rocky Linux 9.2 (Blue Onyx) virtual machine. This template is provided by generic/rocky9 and we will utilize Vagrant to quickly create this local development environment.
💡Tip: If you not familiar with Vagrant and how it supercharge the creation your local development environment have a look at Managing Dev Environments with Vagrant.
We start by downloading the sample Vagrantfile. Afterwards create and connect to the virtual machine.
# Download the Vagrantfile
curl -o Vagrantfile https://raw.githubusercontent.com/maroskukan/linux-cookbook/main/Vagrantfiles/rocky9/Vagrantfile
# Create the Virtual Machine
vagrant up --no-provision
# Connect to the Virtual Machine
vagrant ssh
💡Tip: The reason why we use --no-provision
option is that there is an inline shell provisioning section in this Vagrantfile which will carry out the installation of container tools automatically and test
Once we land in the virtual machine’s shell it is good practice to update all installed packages to latest version.
sudo dnf upgrade -y --exclude=kernel*
💡Tip: For simplicity and speed, we are excluding any kernel updates from the upgrade process.
Next, we are going to install container-tools meta package. This group includes tools such as podman, buildah, skopeo and few more.
sudo dnf install -y container-tools
💡Tip: To list packages that are part of a group you can use the dnf repoquery --requires container-tools
.For more information who to manage software for RPM-based Linux distributions take a look at Managing Software in Red Hat Enterprise Linux.
💡Tip: I also highly suggest to install the bash-completion
package. It will allow you to use TAB
to complete most of the commands.
To quickly verify the installation we can use the podman version
which will display both Podman engine and Go version which was used to compile it.
podman version
Client: Podman Engine
Version: 4.4.1
API Version: 4.4.1
Go Version: go1.19.6
Built: Tue May 9 11:43:57 2023
OS/Arch: linux/amd64
💡Tip: For those familiar with Docker, the container-tools
meta package group also includes the podman-docker
packages which enables to use docker
commands and translate them to podman specific ones.
Next, we can use the podman info
to retrieve system information. The example below uses --format
option to only list registries which will be queried when we search for an image using podman search
command.
podman info --format '{{.Registries.search}}'
[registry.access.redhat.com registry.redhat.io docker.io]
These image registries are defined in the /etc/containers/registries.conf
configuration file. Order in the list does matter. To add quay.io
to this list we can use sed
.
sudo sed -i 's/unqualified-search-registries = \[\(.*\)\]/unqualified-search-registries = \[\1, "quay.io"\]/' /etc/containers/registries.conf
Remember, the change is in effect immediately as there is no daemon to reload.
Finally, to confirm the default container runtime we can use grep. In this case we are using crun.
podman info | grep ociRuntime -A9
ociRuntime:
name: crun
package: crun-1.8.4-1.el9_2.x86_64
path: /usr/bin/crun
version: |-
crun version 1.8.4
commit: 5a8fa99a5e41facba2eda4af12fa26313918805b
rundir: /run/user/1000/crun
spec: 1.0.0
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL
Container image
Next, we are going download a sample hello image that is available with latest tag on quay.io registry in podman namespace.
# Download container image
podman pull quay.io/podman/hello:latest
Trying to pull quay.io/podman/hello:latest...
Getting image source signatures
Copying blob c9fe9f176bca done
Copying config 06f748d83d done
Writing manifest to image destination
Storing signatures
06f748d83d18797fe9a2c2430f5f6ab29837855e793a9a6c86540d61f6ba4401
💡Tip: Tags are used to precisely specify the container image release. For example, to display all tags for python image, you can use the skopeo inspect docker://python
command.
To list all locally available container images, we can use the podman image ls
command.
# Display locally available images
podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
quay.io/podman/hello latest 06f748d83d18 11 hours ago 82 kB
Images are composed of layers. Base layers such as necessary files for operating system are often shared between images to conserve space. To display image layers we can use the podman image tree <image-name>
command.
# Display individual image layers
podman image tree quay.io/podman/hello
Image ID: 06f748d83d18
Tags: [quay.io/podman/hello:latest]
Size: 82kB
Image Layers
└── ID: 020bfaa1f1cf Size: 78.34kB Top Layer of: [quay.io/podman/hello:latest]
💡Tip: The hello
image is very simple and consist of just a single layer. Most of the images such as httpd
contain of several layers.
To get even more information about this container image, we can look at the metadata by using the inspect
option. This As with info
option previously, we can filter the output using --format
argument.
The Config.Cmd
key contains the location of the default process that this container will execute when we start it.
podman image inspect -f {{.Config.Cmd}} quay.io/podman/hello
[/usr/local/bin/podman_hello_world]
Finally, to remove a locally stored image, we can use the rmi
argument.
podman rmi quay.io/podman/hello
Untagged: quay.io/podman/hello:latest
Deleted: 06f748d83d18797fe9a2c2430f5f6ab29837855e793a9a6c86540d61f6ba4401
Container instance
In order to start a container instance, we will use the podman run
command followed by one or more options and ending with image name. The returned value from this command is the container id.
A quick and easy way to validate container runtime is to run the previously referenced hello
image and meet with the seals:
podman container run --rm --rmi hello
Resolved "hello" as an alias (/etc/containers/registries.conf.d/000-shortnames.conf)
Trying to pull quay.io/podman/hello:latest...
Getting image source signatures
Copying blob c9fe9f176bca done
Copying config 06f748d83d done
Writing manifest to image destination
Storing signatures
!... Hello Podman World ...!
.--"--.
/ - - \
/ (O) (O) \
~~~| -=(,Y,)=- |
.---. /` \ |~~
~/ o o \~~~~.----. ~~
| =(X)= |~ / (O (O) \
~~~~~~~ ~| =(Y_)=- |
~~~~ ~~~| U |~~
Project: https://github.com/containers/podman
Website: https://podman.io
Documents: https://docs.podman.io
Twitter: @Podman_io
💡Tip: After running the container, the --rm
and --rmi
options will make sure the container instance and image are removed.
Another example include running a web server container provided by httpd
.
podman container run -d docker.io/library/httpd
bbf5999370bd745b69622d16fe0d18cd28ad30eb62c157adf1f035452aa3ae7b
💡Tip: The -d
option is commonly used to run a container in detached mode.
If the image that we reference is not available locally, it will be automatically downloaded and stored at $HOME/.local/share/containers/storage
.
Do display running containers we can use the podman ps
command.
podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f797144b220 docker.io/library/httpd:latest httpd-foreground 4 seconds ago Up 4 seconds blissful_engelbart
💡Tip: If you do not specify the --name
option, one will be generated for your container, in the above example it is blissful_engelbart
.
Although not commonly used, it is possible to start another process inside of a running container using the exec
command with -it
option. This will start an interactive pseudo TTY attached to bash
process inside our container.
podman exec -it blissful_engelbart /bin/bash
root@bbf5999370bd:/usr/local/apache2#
Depending on container image design, there might be very few commands such as (netstat
, ss
,curl
) that we can actually use to gather information about a container. Lets exit
from bash
process, back to host machine to inspect this image.
Since we are working with httpd
container, there should be some exposed TCP port that we need to expose in order to access this application over the network.
podman inspect blissful_engelbart -f {{.NetworkSettings.Ports}}
map[80/tcp:[]]
In the above output we can see that we did not map the container port TCP/80
to host port. Lets fix this by stopping this instance and creating a new one with port mapping in place.
# Stop and remove existing container instance
podman rm blissful_engelbart
# Start a new intance and map host port 8080 to container port 80
podman run -d -p 8080:80 httpd
ac5cd7644145097b0485b5683827c176f7589adaa08246335eb09bcf66484d5a
To confirm the new network mapping, we inspect the container instance once again, this time using first four characters for container id.
podman inspect ac5c -f {{.NetworkSettings.Ports}}
map[80/tcp:[{ 8080}]]
An easy way how to verify that we can interact with the web application is to perform a curl
request on the host forwarded port.
curl localhost:8080
<html><body><h1>It works!</h1></body></html>
This confirms that we can reach the web application running inside the container.
💡Tip: There are many commands when it comes to podman
. To quickly display related man pages you can use the man -k podman
command.
Top stop a container we can use the podman container stop <container-id>
command.
# Stop container with id stat starts with "ac5c"
podman stop ac5c
ac5c
To remove all stopped containers we can use the podman container prune
command.
# Remove all stopped containers
podman container prune
WARNING! This will remove all non running containers.
Are you sure you want to continue? [y/N] y
ac5cd7644145097b0485b5683827c176f7589adaa08246335eb09bcf66484d5a
Container namespaces
While we have a running container in place, we can optionally look on lower level constructs such as namespaces which were created.
Lets start by retrieving the main container process ID.
podman inspect ac5c -f {{.State.Pid}}
4468
We can use this PID to display related namespaces.
lsns -l | grep 4468
4026532199 mnt 4 4468 vagrant httpd -DFOREGROUND
4026532201 uts 4 4468 vagrant httpd -DFOREGROUND
4026532202 ipc 4 4468 vagrant httpd -DFOREGROUND
4026532203 pid 4 4468 vagrant httpd -DFOREGROUND
4026532204 cgroup 4 4468 vagrant httpd -DFOREGROUND
Finally, we can start a bash process using nsenter
command which allows us to enter a specific namespace associated with the PID. In this case, we are entering multiple namespaces: mount (-m
), UTS (-u
), network (-n
), IPC (-i
), and PID (-p
). The /bin/bash
specifies that we want to start a Bash shell inside the container.
sudo nsenter -t 4468 -m -u -n -i -p /bin/bash
root@ac5cd7644145:/# ls /usr/local/apache2
bin build cgi-bin conf error htdocs icons include logs modules
Lets stop and remove this container instance.
# Stop and remove container instance with ID starting "ac5c"
podman rm -f ac5c
💡Tip: If you use podman stop <image-id>
the container instance will be not be deleted and you can list it using podman ps -a
. To start it again you would use podman start <container-id>
command.
In the next section, we explore ways how we can manage network and storage resources allocated to a container instance.
Container network and storage resources
Port mappings and volume mounts are essential for containerized applications to enable communication and data persistence between containers and the host system. Let’s consider a scenario with a web container and a database container to understand the significance of port mappings and volume mounts.
💡Tip: When using Volume Mount with SELinux, it is important that the host directory file context is set to container_file_t
. This can be done by using the :Z
option when using the volume mount.
💡Tip: The default Bridge Network can be listed using the podman network ls
command.
The web container runs a web server that listens on port TCP/80 inside the container. However, for external users to access the web server, we need to map this container port to a port on the host system. This port mapping allows incoming network traffic to reach the web server running inside the container. By mapping, for example, the container port 80 to the host port TCP/8080, external clients can access the web application by connecting to http://container-host:8080.
# Create Web content on Container Host
mkdir -pv $HOME/data/web
echo "Hello World" >> $HOME/data/web/index.html
# Start the Web Server container
podman run -d --name web_server \
-p 8080:80 \
-v $HOME/data/web:/usr/local/apache2/htdocs:Z \
docker.io/library/httpd:latest
# Verify the Web App from Container Host
curl localhost:8080
# Optionally allow TCP/8080 on public zone in runtime and in permanent rule
sudo firewall-cmd --add-port=8080/tcp --zone=public
!! --permanent
💡Tip: When a host firewall is used on container host, it is required that this external port is opened from the outside network.
The database container requires persistent storage to store its data. Volume mounts allow us to associate a directory or file on the host system with a directory inside the container. In this case, we would want to mount a volume to the database container’s directory responsible for storing database files. By doing so, the database container can read and write data to the mounted directory on the host system. This ensures that the data persists even if the database container is stopped or restarted.
# Create a named volume
podman volume create database
# Start the Daatabase container with named volume
podman run -d --name database \
-v database:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=password \
-e MYSQL_DATABASE=mydatabase \
docker.io/library/mysql:latest
💡Tip: The -e
option is used to set environment variables within the container.
In a typical web application scenario, the web container needs to interact with the database container to retrieve or store data. By using port mappings and volume mounts together, the web container can connect to the database container over the network by specifying the hostname and port (e.g., `database:3306`) and store or retrieve data from the mounted volume. The web container can access the database server using the specified hostname and port, and the database container can store data in the mounted volume for persistent storage.
In summary, port mappings enable external access to containerized services by mapping container ports to host ports, allowing incoming network traffic. Volume mounts provide a way to persist data by associating host directories with container directories. Together, these mechanisms enable seamless communication and data persistence between containerized applications like web and database containers, facilitating the development and deployment of robust, interconnected systems.
Cleanup
Finally, when you would like to remove all traces of this Rocky Linux 9.2 virtual machine, including virtual disks, use the vagrant destroy
command.
vagrant destroy --force
==> rocky9: Stopping the machine...
==> rocky9: Deleting the machine...
💡Tip: The Vagrant box template will remain available on your host machine for future use. If you would like to remove it you need to use the vagrant box remove <box-name>
.
Closing thoughts
In conclusion, containerization has revolutionized application development and deployment. Podman, a powerful container runtime, offers a user-friendly interface, strong security features, and compatibility with Docker. It enables efficient management of containers, networks, volumes, and images.
With Podman, we can harness the full potential of containerization for scalable applications, microservices, and portable development environments. As containerization continues to shape the industry, Podman plays a vital role, empowering users with productivity and flexibility in container-driven workflows.
I would love to hear about your experience with containers and Podman. Please let me know in the comments section below.