docker-compose
is a popular choice to spin up applications needing multiple containers and run them. It allows to define the configuration in a simple yaml format and with few commands, you can create/manage all your application containers at once. Podman does not have a counterpart to the docker-compose
command. There is a project in the works called podman-compose, which is supposed to do the same basic thing as docker-compose
.
However Podman uses a concept called pods. Pods are a way of grouping containers together inside their own namespace, network, and security context. You can start and stop the whole pod at once to manage your application.
Podman Pods – A quick Intro
Podman pods are similiar to kubernetes pods in the sense that they can contain one or more containers at a time. With podman play
command, you can import kubernetes pod definitions in yaml format.
Every podman pod includes an infra container by default. Its purpose is to hold the namespaces associated with the pod and allow podman to connect other containers to the pod. This also lets pods live, if the pod is not running any application containers.
The default infra container is based on the k8s.gcr.io/pause image, Unless stated otherwise, all pods will have container based on the default image.
Most of the attributes assigned to pod are actually assigned to the infra container as essentially it holds all namespaces. So all port bindings, cgroup-parent values, kernel namespace, network namespace, user namepace etc. This is more important as all these namespaces are created as part of podman pod creation. So these attributes cannot be changed later in the pod lifecycle. For example, if you create a pod and then later decide you want to add a container that binds new ports, Podman will not be able to do this. You would need to recreate the pod with the additional port bindings before adding the new container.
Installing Podman
We had earlier discussed the basics of installing and working with Podman in our previous blog post. Alternatively, follow instructions to install podman for your specific linux distribution.
Create Podman Pod
The first thing we need to do is create a pod using podman pod create
command. In its most basic context, you can simply issue podman pod create
and Podman will create a pod without extra attributes. A random name will also be assigned to the pod:
[cloud_user@5ecaa360f91c ~]$ sudo podman pod create 2a70ca9995f6f8be1582492915866a32b67f3e9671335b84981c98adf0540fdd
We can list the pods using podman pod ls
or podman pod list
command:
[cloud_user@5ecaa360f91c ~]$ sudo podman pod ls POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS 2a70ca9995f6 condescending_aryabhata Created 5 seconds ago 95db0b375e17 1 [cloud_user@5ecaa360f91c ~]$ sudo podman pod list POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS 2a70ca9995f6 condescending_aryabhata Created About a minute ago 95db0b375e17 1
We may however, not want pods with random ids, and instead give it a meaningful name. For this, we can use --name
parameter:
[cloud_user@5ecaa360f91c ~]$ sudo podman pod create --name my_pod 6625710c4ec936fe5339d9fd960713e85ef3ee8460b30d691331faf36c4e725e [cloud_user@5ecaa360f91c ~]$ sudo podman pod list POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS 6625710c4ec9 my_pod Created 5 seconds ago 5a1d462d5954 1 2a70ca9995f6 condescending_aryabhata Created 3 minutes ago 95db0b375e17 1
We can seek additional options using --help
:
[cloud_user@5ecaa360f91c ~]$ podman pod create --help Create a new empty pod Description: After creating the pod, the pod ID is printed to stdout. You can then start it at any time with the podman pod start <pod_id> command. The pod will be created with the initial state 'created'. Usage: podman pod create [options] Options: --add-host strings Add a custom host-to-IP mapping (host:ip) (default []) --cgroup-parent string Set parent cgroup for the pod --dns strings Set custom DNS servers --dns-opt strings Set custom DNS options --dns-search strings Set custom DNS search domains --hostname string Set a hostname to the pod --infra Create an infra container associated with the pod to share namespaces with (default true) --infra-command string The command to run on the infra container when the pod is started --infra-conmon-pidfile string Path to the file that will receive the POD of the infra container's conmon --infra-image string The image of the infra container to associate with the pod (default "k8s.gcr.io/pause:3.2") --ip string Specify a static IPv4 address for the container -l, --label strings Set metadata on pod (default []) --label-file strings Read in a line delimited file of labels --mac-address string Container MAC address (e.g. 92:d0:c6:0a:29:33) -n, --name string Assign a name to the pod --network string Connect a container to a network (default "slirp4netns") --network-alias strings Add network-scoped alias for the container --no-hosts Do not create /etc/hosts within the container, instead use the version from the image --pod-id-file string Write the pod ID to the file -p, --publish strings Publish a container's port, or a range of ports, to the host (default []) --replace If a pod with the same name exists, replace it --share string A comma delimited list of kernel namespaces the pod will share (default "cgroup,ipc,net,uts")
To view the infra container, we can use podman ps -a --pod
command:
[cloud_user@5ecaa360f91c ~]$ sudo podman ps -a --pod [sudo] password for cloud_user: CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES POD ID PODNAME 5a1d462d5954 k8s.gcr.io/pause:3.2 8 minutes ago Created 6625710c4ec9-infra 6625710c4ec9 my_pod 95db0b375e17 k8s.gcr.io/pause:3.2 12 minutes ago Created 2a70ca9995f6-infra 2a70ca9995f6 condescending_aryabhata
Note that above output shows two infra containers, one belonging to pod my_pod and another to pod condescending_aryabhatta.
Add application container to Pod
You can add a container to a pod using the --pod
option in the podman create
and podman run
commands. For example, here we add a container running alpine image to the newly created my_pod:
[cloud_user@5ecaa360f91c ~]$ sudo podman run -dt --pod my_pod docker.io/library/alpine:latest top Trying to pull docker.io/library/alpine:latest... Getting image source signatures Copying blob ba3557a56b15 done Copying config 28f6e27057 done Writing manifest to image destination Storing signatures b04464126cac0861ad0285ec697f87f21b629ce9c3c338ce58028f82c4507d4e
--dt
allows pod to run in detached mode. Now if we list containers, we can see that my_pod contains two containers:
[cloud_user@5ecaa360f91c ~]$ sudo podman ps -a --pod CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES POD ID PODNAME b04464126cac docker.io/library/alpine:latest top 6 minutes ago Up 6 minutes ago compassionate_boyd 6625710c4ec9 my_pod 5a1d462d5954 k8s.gcr.io/pause:3.2 38 minutes ago Up 6 minutes ago 6625710c4ec9-infra 6625710c4ec9 my_pod
One single command to create pod and add application container
We can use podman pod run
to create a pod as well as run the application container inside it, rather than using two step process. For this, we have to supply the pod name while using podman pod run
command. Consider the below command to run a mariadb
container:
[cloud_user@5ecaa360f91c ~]$ sudo podman run \ > -d --restart=always --pod new:myapp_pod \ > -e MYSQL_ROOT_PASSWORD="myrootpass" \ > -e MYSQL_DATABASE="wp-db" \ > -e MYSQL_USER="wp-user" \ > -e MYSQL_PASSWORD="w0rdpr3ss" \ > --name=wptest-db mariadb 4223590ae7962edc43f5c19c20af4da32d649ee666c171c9e4b60137c445d653 [cloud_user@5ecaa360f91c ~]$ sudo podman pod list POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS 65fb277514bc myapp_pod Running 12 seconds ago c300c18d7301 2 6625710c4ec9 my_pod Running About an hour ago 5a1d462d5954 2
Notice how we have mentioned that podman needs to create new pod while using --pod
parameter. The use of new:
indicates to Podman that we want to create a new pod rather than attempt to assign the container to an existing pod. In this context, --name
becomes name of the container inside pod:
[cloud_user@5ecaa360f91c ~]$ sudo podman ps -a --pod [sudo] password for cloud_user: CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES POD ID PODNAME 4223590ae796 docker.io/library/mariadb:latest mysqld 9 minutes ago Up 9 minutes ago wptest-db 65fb277514bc myapp_pod c300c18d7301 k8s.gcr.io/pause:3.2 9 minutes ago Up 9 minutes ago 65fb277514bc-infra 65fb277514bc myapp_pod
Creating Application Stack with Pods
Let’s say we want to run a wordpress container connected to mariadb container inside our pod. For this, we need to run two containers in the pod. Lets name this pod as wpapp_pod:
# create pod and add maridb container in one go [cloud_user@5ecaa360f91c ~]$ sudo podman run \ > -d --restart=always --pod new:wpapp_pod \ > -e MYSQL_ROOT_PASSWORD="myrootpass" \ > -e MYSQL_DATABASE="wp-db" \ > -e MYSQL_USER="wp-user" \ > -e MYSQL_PASSWORD="w0rdpr3ss" \ > -p 8080:80 \ > --name=wptest-db mariadb 5d4bbfcfb8187ea29480acc50593db87d54ac2af1a75fdd9ef591962462ed5c1 # add wordpress container to pod wpapp_pod [cloud_user@5ecaa360f91c ~]$ sudo podman run \ > -d --restart=always --pod=wpapp_pod \ > -e WORDPRESS_DB_NAME="wp-db" \ > -e WORDPRESS_DB_USER="wp-user" \ > -e WORDPRESS_DB_PASSWORD="w0rdpr3ss" \ > -e WORDPRESS_DB_HOST="127.0.0.1" \ > --name wptest-web wordpress ea906e2215774a27c9d7ca847dd7a1e1f7a7fa91f91a237da3091467d8b69a46 # list pods [cloud_user@5ecaa360f91c ~]$ sudo podman pod list POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS 05272954b454 wpapp_pod Running 39 seconds ago 8bb0b400a7c6 3 # list all containers in the pod [cloud_user@5ecaa360f91c ~]$ sudo podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ea906e221577 docker.io/library/wordpress:latest apache2-foregroun... 16 seconds ago Up 15 seconds ago 0.0.0.0:8080->80/tcp wptest-web 5d4bbfcfb818 docker.io/library/mariadb:latest mysqld 45 seconds ago Up 44 seconds ago 0.0.0.0:8080->80/tcp wptest-db 8bb0b400a7c6 k8s.gcr.io/pause:3.2 45 seconds ago Up 44 seconds ago 0.0.0.0:8080->80/tcp 05272954b454-infra
We can now try to connect to our application from the server running pod:
[cloud_user@5ecaa360f91c ~]$ curl http://localhost:8080/wp-admin/install.php <!DOCTYPE html> <html lang="en-US" xml:lang="en-US"> <head> <meta name="viewport" content="width=device-width" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="robots" content="noindex,nofollow" /> <title>WordPress › Installation</title> <link rel='stylesheet' id='dashicons-css' href='http://localhost:8080/wp-includes/css/dashicons.min.css?ver=5.7' type='text/css' media='all' /> <link rel='stylesheet' id='buttons-css' href='http://localhost:8080/wp-includes/css/buttons.min.css?ver=5.7' type='text/css' media='all' /> <link rel='stylesheet' id='forms-css' href='http://localhost:8080/wp-admin/css/forms.min.css?ver=5.7' type='text/css' media='all' /> <link rel='stylesheet' id='l10n-css' href='http://localhost:8080/wp-admin/css/l10n.min.css?ver=5.7' type='text/css' media='all' /> <link rel='stylesheet' id='install-css' href='http://localhost:8080/wp-admin/css/install.min.css?ver=5.7' type='text/css' media='all' /> </head> <body class="wp-core-ui language-chooser"> <p id="logo">WordPress</p> <form id="setup" method="post" action="?step=1"><label class='screen-reader-text' for='language'>Select a default language</label> <select size='14' name='language' id='language'> <option value="" lang="en" selected="selected" data-continue="Continue" data-installed="1">English (United States)</option> <option value="af" lang="af" data-continue="Gaan voort">Afrikaans</option> <option value="ar" lang="ar" data-continue="المتابعة">العربية</option> <option value="ary" lang="ar" data-continue="المتابعة">العربية المغربية</option> <option value="as" lang="as" data-continue="Continue">অসমীয়া</option> ... ... <p class="step"><span class="spinner"></span><input id="language-continue" type="submit" class="button button-primary button-large" value="Continue" /></p></form><script type="text/javascript">var t = document.getElementById('weblog_title'); if (t){ t.focus(); }</script> http://localhost:8080/wp-includes/js/jquery/jquery.min.js?ver=3.5.1 http://localhost:8080/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.3.2 http://localhost:8080/wp-admin/js/language-chooser.min.js?ver=5.7 <script type="text/javascript"> jQuery( function( $ ) { $( '.hide-if-no-js' ).removeClass( 'hide-if-no-js' ); } ); </script> </body> </html>
If you want to publish to external world, you might need to configure firewall rules on server and network accordingly.
Stop/Start Containers in the Pod
In Podman, the status of the pod and its containers can be exclusive to each other meaning that containers within pods can be restarted, stopped, and started without impacting the status of the pod. Since infra container is responsible to keep alive pod, it will keep it alive.
Stop Individual Container
This needs use of the container name in the pod along with podman stop
command. Continuing from our previous example, we can stop wordpress container wptest-web like this:
[cloud_user@5ecaa360f91c ~]$ sudo podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ea906e221577 docker.io/library/wordpress:latest apache2-foregroun... 53 minutes ago Up About a minute ago 0.0.0.0:8080->80/tcp wptest-web 5d4bbfcfb818 docker.io/library/mariadb:latest mysqld 53 minutes ago Up About a minute ago 0.0.0.0:8080->80/tcp wptest-db 8bb0b400a7c6 k8s.gcr.io/pause:3.2 53 minutes ago Up About a minute ago 0.0.0.0:8080->80/tcp 05272954b454-infra [cloud_user@5ecaa360f91c ~]$ sudo podman stop wptest-web ea906e2215774a27c9d7ca847dd7a1e1f7a7fa91f91a237da3091467d8b69a46 [cloud_user@5ecaa360f91c ~]$ sudo podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5d4bbfcfb818 docker.io/library/mariadb:latest mysqld 54 minutes ago Up About a minute ago 0.0.0.0:8080->80/tcp wptest-db 8bb0b400a7c6 k8s.gcr.io/pause:3.2 54 minutes ago Up About a minute ago 0.0.0.0:8080->80/tcp 05272954b454-infra
Start individual container
This needs use of the container name in the pod along with podman start
command. Continuing from our previous example, we can start wordpress container wptest-web like this:
[cloud_user@5ecaa360f91c ~]$ sudo podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5d4bbfcfb818 docker.io/library/mariadb:latest mysqld 54 minutes ago Up About a minute ago 0.0.0.0:8080->80/tcp wptest-db 8bb0b400a7c6 k8s.gcr.io/pause:3.2 54 minutes ago Up About a minute ago 0.0.0.0:8080->80/tcp 05272954b454-infra [cloud_user@5ecaa360f91c ~]$ sudo podman start wptest-web wptest-web [cloud_user@5ecaa360f91c ~]$ sudo podman pod ps POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS 05272954b454 wpapp_pod Running 55 minutes ago 8bb0b400a7c6 3 [cloud_user@5ecaa360f91c ~]$ sudo podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ea906e221577 docker.io/library/wordpress:latest apache2-foregroun... 54 minutes ago Up 20 seconds ago 0.0.0.0:8080->80/tcp wptest-web 5d4bbfcfb818 docker.io/library/mariadb:latest mysqld 55 minutes ago Up 3 minutes ago 0.0.0.0:8080->80/tcp wptest-db 8bb0b400a7c6 k8s.gcr.io/pause:3.2 55 minutes ago Up 3 minutes ago 0.0.0.0:8080->80/tcp 05272954b454-infra
Stop all containers at once
This needs use of the pod name along with podman pod stop
command. Continuing from our previous example, we can stop all containers in pod wpapp_pod like this:
[cloud_user@5ecaa360f91c ~]$ sudo podman pod list POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS 05272954b454 wpapp_pod Running 58 minutes ago 8bb0b400a7c6 3 [cloud_user@5ecaa360f91c ~]$ sudo podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ea906e221577 docker.io/library/wordpress:latest apache2-foregroun... 58 minutes ago Up 3 minutes ago 0.0.0.0:8080->80/tcp wptest-web 5d4bbfcfb818 docker.io/library/mariadb:latest mysqld 58 minutes ago Up 6 minutes ago 0.0.0.0:8080->80/tcp wptest-db 8bb0b400a7c6 k8s.gcr.io/pause:3.2 58 minutes ago Up 6 minutes ago 0.0.0.0:8080->80/tcp 05272954b454-infra [cloud_user@5ecaa360f91c ~]$ sudo podman pod stop wpapp_pod 05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a [cloud_user@5ecaa360f91c ~]$ sudo podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES [cloud_user@5ecaa360f91c ~]$ sudo podman pod list POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS 05272954b454 wpapp_pod Exited 59 minutes ago 8bb0b400a7c6 3
Start all containers at once
This needs use of the pod name along with podman pod start
command. Continuing from our previous example, we can start all containers in pod wpapp_pod like this:
[cloud_user@5ecaa360f91c ~]$ sudo podman pod start wpapp_pod [sudo] password for cloud_user: 05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a [cloud_user@5ecaa360f91c ~]$ sudo podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ea906e221577 docker.io/library/wordpress:latest apache2-foregroun... About an hour ago Up 7 seconds ago 0.0.0.0:8080->80/tcp wptest-web 5d4bbfcfb818 docker.io/library/mariadb:latest mysqld About an hour ago Up 7 seconds ago 0.0.0.0:8080->80/tcp wptest-db 8bb0b400a7c6 k8s.gcr.io/pause:3.2 About an hour ago Up 8 seconds ago 0.0.0.0:8080->80/tcp 05272954b454-infra
Remove Containers and Pods
Once you are ready to wrap up your application, you can use podman pod rm to remove the pod and all containers within it. This is designed to make sure that you would not be able to remove pod containing active containers. So you need to first stop all containers and then remove the pod:
# Cannot remove pods containing active application containers [cloud_user@5ecaa360f91c ~]$ sudo podman pod rm wpapp_pod Error: pod 05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a has containers that are not ready to be removed: cannot remove container 5d4bbfcfb8187ea29480acc50593db87d54ac2af1a75fdd9ef591962462ed5c1 as it is running - running or paused containers cannot be removed without force: container state improper # Stop all application containers at once [cloud_user@5ecaa360f91c ~]$ sudo podman pod stop wpapp_pod 05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a # verify that no application containers are running [cloud_user@5ecaa360f91c ~]$ sudo podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # remove the pod [cloud_user@5ecaa360f91c ~]$ sudo podman pod rm wpapp_pod 05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a
[…] you want to know more about pods and how to work with them, refer to our previous blog post. Lets create a new pod wpapp_pod containing wordpress and mariadb inside […]
LikeLike
[…] This blog post continues from where we left in our earlier blog post, where we discussed how systemd and podman fits in together to run and manage containers as systemd services. We discussed how we can do the same for a specific containers and learned to create generic systemd unit files. We also discussed few use cases where this integration is useful. As we know, pods in podman are a way to group and manage multiple application containers as one. So we can start them together, manage them together and then remove them together once done. If we do need, we can manage them individually as well. Basics of pods are covered here. […]
LikeLike
Why are you using sudo on all these podman commands? All of these work without sudo.
LikeLike
This is great! I’m preparing for a Redhat Openshift cert, and this very well explains requirements for one of the steps in the Podman portion. If you don’t mind a suggestion, you may want to include in the meta data or description for this article that this can be used during the tasks for RH EX180
LikeLike
This is great! I’m preparing for a Redhat Openshift exam and this helps with one of the tasks in the Podman portion. If you’re open suggestions, may I recommend you add some metadata or content in the description of this article to describe Redhat EX180? It is surprisingly difficult to find succinct explanations for tasks in the exam outside of Redhat training with is exorbitantly expensive. Guides like this go a long way for us who are not being reimbursed by employers.
LikeLike