Build a bootable Python Flask application using RHEL Image Mode with Podman Desktop

Use Podman Desktop to create a bootable Flask-based application using image mode for RHEL. We will integrate Flask, Gunicorn, and NGINX into a bootable container.

Download Podman Desktop

This lesson will demonstrate how to create a Red Hat container for development and use it to develop a basic Python Flask Hello World application. The running container will become your main development environment for this application, with the actual application code stored locally on your local desktop where you can keep it under source control and edit it with your favorite desktop-native development tools.

To get the full benefit of this lesson, you must:

In this lesson, you will:

  • Develop a foundational understanding of the core application components.
  • Create a basic Hello World application using Python and Flask.
  • Create configuration files for Flask, Gunicorn, and NGINX.
  • Create a Containerfile to install the necessary components and configure a bootable container.

The core application components

This application is made up of three key components: Flask, Gunicorn, and NGINX.  

  • Flask: In this architecture, Flask provides a lightweight and flexible web framework for the Python programming language. It’s left to Flask to provide the routing (matching web addresses to Python functions), request handling, and templating basics that allow you to build your web applications in a Pythonic way. We chose Flask for this example because it has a simple core that's quick to learn and use, but it’s also extensible and well-supported by the Python community.
  • Gunicorn: Green Unicorn (Gunicorn) is the Python web server gateway interface (WSGI) HTTP server used in this architecture. Flask doesn’t know how to directly talk to the web. Instead, it uses WSGI, a standard interface. Gunicorn acts as the bridge, taking requests from the web and translating them into a format the Python application (e.g., built with Flask) can understand. In our design, it takes the application's response and sends it back to NGINX, which communicates with the client.
  • NGINX: In this architecture, NGINX acts as a frontline webserver and reverse proxy server. This component faces the external network, receiving all incoming web traffic (HTTP requests) from your browser. NGINX does not handle requests processed by your Python application (dynamic content). Instead, it forwards (proxies) these requests to Gunicorn, which calls Flask. In addition, NGINX can handle SSL/TLS termination, encrypting and decrypting traffic, and it can implement various security measures and traffic management policies. You can refer to the RHEL documentation for NGINX.

Create a basic Hello World application using Python and Flask

Step 1: Build and utilize a Python Flask-oriented container-based Red Hat developer environment

Let's start by creating a directory flask-dev where we’ll store all the files we’ll use for this project. You’ll do this on the computer where you are running Podman Desktop.

We’ll place the following contents in the file Containerfile in our lamp-dev project directory:

# Use the official Red Hat Universal Base Image 10 as the foundation.
# It's a solid enterprise-grade base and comes with Python 3.12.
# We use the -init version that includes systemd because we're
# targeting using a bootable container for distribution and those 
# use systemd.  This way we can test in a similar configuration 
# between our development container and our production one.
FROM registry.access.redhat.com/ubi10/ubi-init:latest

# Good practice to label your images.
LABEL maintainer="Your Name <youremail@example.com>" \
      description="Python 3.12 development environment based on UBI 10."

# Install core development tools and Python's package manager (pip).
# - python3.12-pip: For managing Python packages.
# - nginx: a security-focussed web server for exposing our application 
# - ncurses: includes useful utilities for developers
# We clean up the dnf cache to keep the image size down.
RUN dnf install -y python3.12-pip nginx ncurses procps && \
    dnf clean all

# Set the working directory to the new user's home.
WORKDIR /app

# Expose port 8000. This makes the port available for mapping to the
# host, which is necessary for testing web applications.
EXPOSE 8000

# Set the default command. When the container starts, it will drop you into a Bash shell.
CMD ["/sbin/init"]

With flask-dev/containerfile in place, we’re ready to build our app development container. 

  1. Create the sub-directories flask-dev/bootc/app on your local desktop using your preferred native desktop tools. We’ll use this directory as the central repository for our application files. You’ll be able to edit files either using the editors in your container or by using your favorite desktop text editor or IDE. Either way, they will be accessible from your container. 
  2. Open Podman Desktop on your local machine and go to the Images section in the left navigation (Figure 1).

    This shows the Podman desktop with the Images section opened with no images.
    Figure 1: This shows the Podman Desktop Images section.
  3. Select Build in the top right corner.
  4. Specify a Containerfile path for the file we just created, the image name, flask-dev-image, and a platform. On my laptop, I chose the Intel and AMD x86_64 image option for my Fedora-based laptop (Figure 2).

    This Podman Desktop build screen shows an image name inputted and platform specified.
    Figure 2: This shows the Podman Desktop build screen with an image name inputted and platform specified.
  5. Now select Build at the bottom, and it will build your new image (Figure 3). Then select Done.

    This Podman Desktop Build Process screen shows the Build and Done buttons.
    Figure 3: The Podman Desktop Build Process screen where you click the Build and Done buttons.
  6. Back on the main Images section, select the right arrow next to the flask-dev-image to start the image (Figure 4).

    The Podman Desktop Images screen that shows the images listed. Run the cursor over the right arrow icon to run the image.
    Figure 4: This is the Podman Desktop Images screen with the images listed.
  7. Name the container flask-dev. Under Volumes, select the subdirectory we created earlier, flask-dev/bootc/app, as the Path on the Host, and specify /app:z as the Path inside the container (Figure 5).
    Note that the :z option will not show up in the actual path in the container. It’s a directive to Podman to allow multiple containers to share the volume content with the local host. In this case, your desktop. This is achieved by relabeling the directory as container_t on SELinux-enabled Linux systems. It is only needed if our desktop is a Linux system, such as RHEL or Fedora. Do not select Start Container as we still have an additional step.

    The Podman Desktop Create Container screen with the option to select the subdirectory.
    Figure 5: The basic tab on the Podman Desktop Create Container screen.
  8. Select the Security section at the top right of the form and scroll down to Specify user namespace to use and enter host". This keyword will map your desktop user account to the user identity (UID) within the container, allowing you to share files between your native desktop environment and your new development container. Figure 6 illustrates an example.

    The Podman Desktop Create Container screen. The Security tab shows the user namespace specified.
    This shows the Security tab on the Podman Desktop Create Container screen.
  9. Select Start container at the bottom. We now have a running container named flask-dev. Go to the Containers section, double-click on the container, and select Terminal. For the next part of this learning path, our examples assume we’ll be working from a terminal screen (Figure 7).

    This Podman Desktop Container Details screen shows the Terminal tab.
    Figure 7: The Terminal tab on the Podman Desktop Container Details screen.

Step 2: Create and activate a virtual environment

As we get started working in Python, a great best practice is to create a separate virtual environment for each of our Python projects.

In this next step, within our Podman Desktop terminal, we’ll create a separate virtual environment for our Python Flask project within the /app directory.. This keeps dependencies for different projects isolated. We’ll create our project in the /app directory within the container, which we’ll name flask-venv.

cd /app
python3 -m venv flask-venv

Activate the environment

We need to activate the environment before we can use it. 

source flask-venv/bin/activate

Once activated, you'll see the name of our virtual environment (flask-venv) will appear in parentheses at the beginning of our command prompt.

Install Flask and Gunicorn

Now that our virtual environment is active, we can install Flask and Gunicorn. This installation will be contained entirely within the virtual environment we created. Run the following command to install Flask and Gunicorn: 

pip install flask gunicorn

Step 3: Develop a basic Python Flask application

We’re now ready to start developing our Python Flask application. For this demo, a simple Hello World app will get us started.

Start by creating the file helloworld.py with your favorite text editor and saving it into our flask-dev/bootc/app directory.

The contents of this file will:

  • Load the Flask module.
  • Associate the application with the default webserver home directory.
  • Create a hello_world function that returns the string "Hello, World!".

The file content is as follows:

from flask import Flask

app = Flask(__name__)

@app.route('/')

def hello_world():

    return 'Hello, World!'

Step 4: Call a Python Flask application from Gunicorn

Gunicorn is the WSGI server that will serve our Flask application. To serve up our application as a web page, we’ll need our Gunicorn to call our Hello World application in the wsgi.py file, which we will also place in the flask-dev/bootc/app directory as follows: 

from helloworld import app

if __name__ == "__main__":
    app.run()

Step 5: Set up configuration files for Flask and NGINX

We’ll need runtime configuration files for Flask, Gunicorn, and NGINX. Let’s start by creating a systemd unit file to start the Gunicorn server for our Flask application.

We’ll call the flask-app.service file. Once system networking begins, this will start the Gunicorn server and invoke our WSGI-based Flask application. Note that the RunTimeDirectory directive instructs systemd to create a directory flask-app under /run for the UNIX domain socket used in communications between Gunicorn and NGINX. We’ll come back to that socket later as SELinux requires us to explicitly allow communications between Gunicorn and NGINX over this channel. 

Systemd unit file for Flask

For now, we’ll create the flask-app.service file in the flask-dev/bootc/app/ directory with our other configuration files as follows: 

After=network.target

[Service]
User=nginx
Group=nginx
RuntimeDirectory=flask-app
WorkingDirectory=/app
Environment="PATH=/app/"
ExecStart=/usr/local/bin/gunicorn --workers 3 --bind unix:/run/flask-app/flask-app.sock -m 007 wsgi:app

[Install]
WantedBy=multi-user.target

NGINX server configuration

Next, we will configure our NGINX server to communicate with Gunicorn. The file will specify the server name and port on which we plan to serve our web services, as to how to communicate with our Gunicorn server. For this example, we used localhost as the name. But for production, we recommend specifying the server name using https and exposing our web service on a high-numbered port such as 8080. 

This content should be placed in the flask-app.conf file, which we’ll place in our flask-dev/bootc/app directory as follows:

server {
    listen 8000;
    server_name localhost;

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://unix:/run/flask-app/flask-app.sock;
    }
}

Step 6: Install configuration files and start the application

We created our configuration files, but now it’s time to copy them all into place and activate the service.  Within the Podman Desktop terminal screen, enter the following commands.  This will allow us to test our environment in our development container before we package it up as a bootable image. 

cp /app/flask-app.service /etc/systemd/system/
cp /app/flask-app.conf /etc/nginx/conf.d/flask-app.conf
systemctl enable --now nginx.service
systemctl enable --now flask-app.service

Step 7: View your app in the browser

Open your web browser and navigate to the address shown in the terminal:

http://server_name:8000/

You should see a page with the text, Hello, World! (Figure 8).

Connect using your browser to http://127.0.0.1:8000 and check your configuration.
Figure 8: Connect using your browser to http://127.0.0.1:8000 and check your configuration.

Congratulations! You've just built and run your first Flask application.

Step 8: Package your application in image mode for RHEL

As a final step, let's package up our Python dependencies for later use on our new container. 

The following command maintains a list of packages that we can then use to install in our bootable Django container:

cd /app; pip freeze > requirements.txt

Now that you have your configuration files, let’s move on to the next lesson, where we’ll create the disk image.

Previous resource
Access the Red Hat Container Registry
Next resource
Build and run a bootable Flask application disk image in Podman Desktop