Run Containers with Podman on RHEL

Red Hat Enterprise LinuxBeginner
Practice Now

Introduction

In this lab, you will learn how to deploy a multi-tier web application using Podman on Red Hat Enterprise Linux (RHEL). You will build a complete solution by deploying a MariaDB database container as the backend and an Apache web server container as the frontend. This hands-on experience will guide you through the essential steps of containerized application deployment, from initial configuration to making the service publicly accessible.

You will begin by running a MariaDB container and configuring it at startup with environment variables. Next, you will configure persistent storage to ensure data durability for the database and create a custom network for container communication. You will then deploy the Apache web server, expose its port to test connectivity, and finally, learn how to manage the container as a systemd service for robust, automated operation.

Run a MariaDB Database Container with Environment Variables

In this step, you will learn how to run a containerized application and configure it at startup using environment variables. This is a fundamental skill in container management, allowing for flexible and secure deployments. We will use the official MariaDB image as an example, as it requires several configuration parameters to initialize a database.

First, ensure you are in the correct working directory. All work for this lab will be done inside the ~/project directory.

cd ~/project

Before running a container, it's good practice to explicitly pull the image from the registry. This ensures you have the correct version locally. We will use the mariadb:10.6 image for this lab to ensure consistency.

podman pull mariadb:10.6

Select the mariadb:10.6 image from the docker registry.

You should see output indicating the image is being downloaded and extracted.

10.6: Pulling from library/mariadb
...
Status: Downloaded newer image for mariadb:10.6
docker.io/library/mariadb:10.6

Now, you can run the MariaDB container. The podman run command creates and starts a new container. We will use several flags:

  • -d: Runs the container in detached mode (in the background).
  • --name mariadb_server: Assigns a memorable name to our container.
  • -e VARIABLE=value: Sets an environment variable inside the container. The MariaDB image uses these to configure the database upon first launch.

Run the following command to start your MariaDB container. We are setting the root password, and also creating a new database named webappdb with a dedicated user webappuser.

podman run -d \
  --name mariadb_server \
  -e MARIADB_ROOT_PASSWORD=supersecret \
  -e MARIADB_DATABASE=webappdb \
  -e MARIADB_USER=webappuser \
  -e MARIADB_PASSWORD=userpass \
  mariadb:10.6

The command will output a long container ID, which confirms that the container has been created.

a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

To verify that the container is running, use the podman ps command.

podman ps

You should see mariadb_server in the list of running containers.

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS      NAMES
a1b2c3d4e5f6   mariadb:10.6   "docker-entrypoint.s…"   15 seconds ago   Up 14 seconds   3306/tcp   mariadb_server

Finally, let's check the container's logs to ensure the database initialized correctly using the environment variables we provided.

podman logs mariadb_server

Scroll through the logs. You are looking for a line that indicates the server is ready for connections, which confirms a successful startup. The output will be lengthy, but a key success message near the end looks like this:

...
2024-05-20 10:30:00+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/
...
2024-05-20 10:30:15+00:00 [Note] mariadbd: ready for connections.
Version: '10.6.x-MariaDB-1:10.6.x+maria~ubu2004'  socket: '/run/mysqld/mysqld.sock'  port: 3306  mariadb.org binary distribution

You have successfully launched and configured a MariaDB container using environment variables.

Configure Persistent Storage for the MariaDB Container

In this step, you will learn how to configure persistent storage for a container. By default, any data created inside a container is stored in a writable layer that is tied to the container's lifecycle. If you remove the container, all of that data is lost. For stateful applications like databases, this is not ideal. To solve this, we use Podman volumes or bind mounts to store the data on the host filesystem, independent of the container.

First, we need to remove the container we created in the previous step, as we will relaunch it with a new storage configuration.

Stop the running mariadb_server container:

podman stop mariadb_server

You will see the name of the container as output, confirming the command was received.

mariadb_server

Now, remove the stopped container:

podman rm mariadb_server

Again, the container's name will be echoed back.

mariadb_server

Next, create a directory on your host machine within the ~/project directory. This directory will hold the MariaDB database files.

mkdir ~/project/mariadb_data

When using Podman in rootless mode, we need to set the correct permissions for the mounted directory. The MariaDB container runs as a specific user (UID 999), so we need to ensure the directory is accessible. We'll use the --userns=keep-id flag and set appropriate permissions:

chmod 755 ~/project/mariadb_data

Now, run the MariaDB container again. This command is similar to the one from the previous step, but with the addition of the -v flag and --userns=keep-id to handle user namespace mapping properly. The -v flag mounts the ~/project/mariadb_data directory from your host into the /var/lib/mysql directory inside the container, which is where MariaDB stores its data. We use $(pwd)/mariadb_data to provide the required absolute path to the podman command.

podman run -d \
  --name mariadb_server \
  --userns=keep-id \
  -e MARIADB_ROOT_PASSWORD=supersecret \
  -e MARIADB_DATABASE=webappdb \
  -e MARIADB_USER=webappuser \
  -e MARIADB_PASSWORD=userpass \
  -v $(pwd)/mariadb_data:/var/lib/mysql:Z \
  mariadb:10.6

The :Z suffix on the volume mount tells Podman to relabel the content with a private unshared label, which is important for SELinux compatibility.

After the container starts, you can verify that the data is being stored on your host machine. List the contents of the ~/project/mariadb_data directory.

ls -l ~/project/mariadb_data

Because the container's database engine has initialized, you will see several files and directories created inside ~/project/mariadb_data. This confirms that your data is now persistent. Even if you remove the container, this data will remain.

total 110632
-rw-rw---- 1 labex labex    16384 May 20 10:45 aria_log.00000001
-rw-rw---- 1 labex labex       52 May 20 10:45 aria_log_control
-rw-rw---- 1 labex labex      983 May 20 10:45 ib_buffer_pool
-rw-rw---- 1 labex labex 12582912 May 20 10:45 ibdata1
-rw-rw---- 1 labex labex 50331648 May 20 10:45 ib_logfile0
-rw-rw---- 1 labex labex 50331648 May 20 10:45 ib_logfile1
drwx------ 2 labex labex     4096 May 20 10:45 mysql
drwx------ 2 labex labex     4096 May 20 10:45 performance_schema
drwx------ 2 labex labex     4096 May 20 10:45 sys
drwx------ 2 labex labex     4096 May 20 10:45 webappdb

You have successfully configured your MariaDB container to use persistent storage, ensuring your database data will survive container restarts and removals.

Create a Custom Network and Deploy an Apache Web Server

In this step, you will create a custom network for your containers and deploy an Apache web server. While Podman provides a default network, using custom networks is a best practice. They provide better isolation and, most importantly, enable automatic DNS resolution between containers. This allows containers to communicate with each other using their names, which is more reliable than using IP addresses that can change.

First, let's create a custom bridge network for our application. We'll name it webapp-network.

podman network create webapp-network

The command will output the name of the newly created network.

webapp-network

You can list all Podman networks to confirm that yours was created successfully.

podman network ls

You should see webapp-network in the list, along with the default networks.

NETWORK ID     NAME               DRIVER    SCOPE
...
f1e2d3c4b5a6   webapp-network     bridge    local
...

Next, we need to recreate our mariadb_server container on this new network. Due to the network backend configuration in this environment, we cannot connect an existing container to a new network. Instead, we'll stop and recreate the container with the new network configuration.

Stop the running mariadb_server container:

podman stop mariadb_server

Remove the stopped container:

podman rm mariadb_server

Now, recreate the MariaDB container with the new network. This command is similar to the one from the previous step, but with the addition of the --network webapp-network flag:

podman run -d \
  --name mariadb_server \
  --network webapp-network \
  --userns=keep-id \
  -e MARIADB_ROOT_PASSWORD=supersecret \
  -e MARIADB_DATABASE=webappdb \
  -e MARIADB_USER=webappuser \
  -e MARIADB_PASSWORD=userpass \
  -v $(pwd)/mariadb_data:/var/lib/mysql:Z \
  mariadb:10.6

Now, let's deploy our web server. We will use the official Apache httpd image. First, create a directory on the host to store your website's files.

mkdir ~/project/webapp_content

Create a simple index.html file in this new directory. This will be the homepage of our web application.

echo "<h1>Welcome to My Web App</h1>" > ~/project/webapp_content/index.html

Set the correct permissions for the webapp content directory to ensure the Apache container can access the files:

chmod 755 ~/project/webapp_content

Now, run the Apache httpd container. We will attach it to our webapp-network and mount the webapp_content directory as a volume. This ensures the web server can serve the index.html file we just created.

podman run -d \
  --name web_server \
  --network webapp-network \
  -v $(pwd)/webapp_content:/usr/local/apache2/htdocs/:Z \
  httpd:2.4

Let's break down the options:

  • --network webapp-network: Connects the new container to our custom network.
  • -v $(pwd)/webapp_content:/usr/local/apache2/htdocs/:Z: This mounts our local webapp_content directory into the container at /usr/local/apache2/htdocs/, which is the default directory Apache serves files from. The :Z suffix tells Podman to relabel the content with a private unshared label for SELinux compatibility.

Verify that both containers are running.

podman ps

You should now see both mariadb_server and web_server in the list of running containers.

CONTAINER ID  IMAGE                           COMMAND           CREATED         STATUS         PORTS       NAMES
6a3f46c0ab3a  docker.io/library/mariadb:10.6  mariadbd          29 seconds ago  Up 29 seconds  3306/tcp    mariadb_server
da5d52ce9c41  docker.io/library/httpd:2.4     httpd-foreground  7 seconds ago   Up 7 seconds   80/tcp      web_server

Both containers are now on the same custom network and can communicate with each other by name.

Expose the Web Server Port and Test Connectivity

In this step, you will learn how to expose a container's port to the host machine, making the service accessible from outside the container's isolated network. Our Apache web server is running, but we cannot yet access it from our host's browser or command line. We will fix this by publishing the container's port.

Port mappings are defined when a container is created. Therefore, we must first stop and remove the web_server container we created in the previous step. Don't worry about the website content; it's safe in the ~/project/webapp_content directory on our host because we used a bind mount.

First, stop the container:

podman stop web_server
web_server

Next, remove the stopped container:

podman rm web_server
web_server

Now, we will run the web_server container again, but this time we will add the -p (or --publish) flag to map a port from the host to a port in the container. We will map port 8080 on the host to port 80 (the default HTTP port) inside the container.

podman run -d \
  --name web_server \
  --network webapp-network \
  -v $(pwd)/webapp_content:/usr/local/apache2/htdocs/:Z \
  -p 8080:80 \
  httpd:2.4

The new flag -p 8080:80 tells Podman to forward all traffic from port 8080 on the host to port 80 inside the web_server container.

Let's verify the container is running and the port is correctly mapped using podman ps.

podman ps

Notice the PORTS column for the web_server container. It now shows the mapping from 0.0.0.0:8080 to 80/tcp, indicating that the port is successfully exposed.

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                  NAMES
c5d4e3f2a1b6   httpd:2.4      "httpd-foreground"       10 seconds ago   Up 9 seconds    0.0.0.0:8080->80/tcp   web_server
a1b2c3d4e5f6   mariadb:10.6   "docker-entrypoint.s…"   25 minutes ago   Up 25 minutes   3306/tcp               mariadb_server

Finally, let's test the connectivity from our host machine using the curl command. This sends an HTTP request to localhost on port 8080.

curl http://localhost:8080

You should see the HTML content from your index.html file as the output, confirming that your web server is now accessible from the host.

<h1>Welcome to My Web App</h1>

You have successfully exposed your containerized web server to the host machine, a critical step for making applications available to users.

Manage the Web Server Container as a systemd Service

In this final step, you will learn how to configure a container to start automatically, ensuring your service is resilient to crashes or system reboots. On a standard Red Hat Enterprise Linux system, systemd is the primary tool for managing services. However, the Podman environment in this lab does not use systemd to manage containers directly.

Instead, we will achieve the same outcome—automatic service restarts—using Podman's built-in restart policies. This is the standard, container-native way to ensure a container is automatically started by the Podman daemon. We will configure our web_server to always restart if it stops for any reason.

First, we must remove the existing container, as restart policies can only be applied when a container is created.

Stop the web_server container:

podman stop web_server
web_server

And now remove it:

podman rm web_server
web_server

Next, re-create the web_server container with the same configuration as before, but add the --restart always flag. This flag instructs the Podman daemon to monitor the container and restart it if it ever exits.

podman run -d \
  --name web_server \
  --network webapp-network \
  -v $(pwd)/webapp_content:/usr/local/apache2/htdocs/:Z \
  -p 8080:80 \
  --restart always \
  httpd:2.4

The container will start as usual. To confirm the restart policy is active, you can inspect the container's configuration.

podman inspect web_server --format '{{.HostConfig.RestartPolicy.Name}}'

The command should return always, confirming the policy is set.

always

Now, let's demonstrate how the restart policy works by manually restarting the container to simulate what would happen after a system reboot or container failure.

First, let's check the current restart policy configuration:

podman inspect web_server --format '{{.HostConfig.RestartPolicy.Name}}'

This should show always, confirming our restart policy is configured.

always

Now let's test manual restart to simulate recovery after a failure:

podman start web_server
web_server

Check that the container is running:

podman ps

You should see both containers running with the restart policy in place:

CONTAINER ID   IMAGE          COMMAND                  CREATED              STATUS              PORTS                  NAMES
e7f6g5h4i3j2   httpd:2.4      "httpd-foreground"       About a minute ago   Up 5 seconds        0.0.0.0:8080->80/tcp   web_server
a1b2c3d4e5f6   mariadb:10.6   "docker-entrypoint.s…"   About an hour ago    Up About an hour    3306/tcp               mariadb_server

Finally, confirm that the service is accessible:

curl http://localhost:8080
<h1>Welcome to My Web App</h1>

Understanding Restart Policies:

The --restart always policy you've configured ensures that:

  • The container will restart automatically if it exits unexpectedly
  • The container will start automatically when the Podman service starts (such as after a system reboot)
  • This provides resilience for production deployments

Note: In some lab environments, automatic restart behavior may vary depending on the Podman configuration and whether the Podman system service is running. The key learning objective is understanding how to configure restart policies for production deployments.

You have successfully configured your container to be managed like a service, ensuring it remains available automatically. This completes the basic lifecycle management of a containerized application.

Summary

In this lab, you learned the fundamental process of deploying a multi-container web application on RHEL using Podman. You began by running a MariaDB database container, configuring its initial state—including the root password, a new database, and a dedicated user—by passing environment variables at runtime. You then configured persistent storage for the database container, ensuring that critical data is preserved across container restarts.

To complete the application stack, you created a custom network to enable secure, isolated communication between the containers. You deployed an Apache web server container onto this network and exposed its port to allow external user access. Finally, you integrated the web server container with systemd, managing it as a system service to ensure it starts automatically on boot and runs reliably, demonstrating a production-ready deployment pattern.