Page
Build and run a bootable Django application disk image in Podman Desktop using image mode.

This lesson will walk you through deploying our simple Python Django application as a bootable container and deploying it as a web page using Gunicorn and NGINX. We’ll set up basic code and configuration files and build a Containerfile which we will use to create our bootable container.
Prerequisites:
- A basic understanding of how to traverse a Linux file system.
- An understanding of how to create and edit Linux text files.
- A basic understanding of Python.
- A basic understanding of Django.
In this lesson, you will:
- Develop a foundational understanding of the core application components.
- Create configuration files for Django, 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: Django, Gunicorn, and NGINX. From our previous lesson, you’re already familiar with Django, but here’s some information on Gunicorn and NGINX:
- Gunicorn: Green Unicorn (Gunicorn) is the Python web server gateway interface (WSGI) HTTP server used in this architecture. Django isn’t designed to directly talk to the web in production environments. 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 Django) 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 web server 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. Instead, it proxies these requests to Gunicorn, which calls Django. In addition, NGINX can handle SSL/TLS termination and encrypting/decrypting traffic. It can also implement various security measures and traffic management policies. You can refer to the RHEL documentation for NGINX.
In the django-dev
directory on our laptop, we’ll create a subdirectory called bootc
where we’ll keep the files associated with constructing our Image mode for RHEL bootable container for deployment.
Step 1: Create configuration files for Gunicorn and NGINX
We’ll need runtime configuration files for Gunicorn and NGINX, which we will include in our image mode container. Let’s start by creating a systemd unit file to start the Gunicorn server for our Gunicorn application.
Important
All of these files will be created on your desktop and not in the django-dev container.
We’ll create the django.service
file. Once system networking begins, this will start the Gunicorn server and invoke our WSGI-based Django application.
Note that the RunTimeDirectory directive instructs systemd to create a directory myproject
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.
For now, we’ll create the django.service
file in the django-dev/bootc
directory with our other configuration files as follows:
[Unit]
Description=A Django project served by gunicorn
After=network.target
[Service]
User=nginx
Group=nginx
WorkingDirectory=/app/myproject
Environment="PATH=/app/"
ExecStart=/app//myproject/django-venv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/myproject/myproject.sock myproject.wsgi:application -m 007
[Install]
WantedBy=multi-user.target
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 Hypertext Transfer Protocol Secure (HTTPS) and exposing our web service on a high-numbered port, such as 8080
.
This content should be placed in the nginx-myproject.conf
file, which we’ll place in our django-dev/bootc
directory. Be sure to modify the server_name
parameter to match the name of the server you will be creating.
server {
listen 80;
server_name server_name;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /app/myproject;
}
location / {
# These proxy headers are crucial for Django
proxy_set_header Host $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/myproject/myproject.sock;
}
}
Create a containerfile to install the components and configure a bootable container
Starting with the RHEL 10 bootable image, the sample container file performs the following steps:
- Install the required RPM packages through DNF.
- Copy the files we’ve created into our container from our container directory (
django-dev/bootc
). - Use pip to install the same Gunicorn and Django modules from the Python community repository we used in our development environment container.
- Configure NGINX to communicate with Gunicorn.
- Configure SELinux to allow NGINX to communicate with Gunicorn over UNIX domain sockets.
- Enable and start our application services.
Using the text editor of your choice, create this file and save it locally as containerfile
in the django-dev/bootc
directory where you placed your other files.
Later, you will upload this file to Podman Desktop when you build the image in Lesson 4:
FROM registry.redhat.io/rhel10/rhel-bootc
LABEL maintainer="Your Name <youremail@example.com>" \
description="Python 3.12 bootable Django Server on RHEL 10."
# --- 1. Install System Dependencies ---
# Install all necessary RHEL packages in a single layer to keep the image smaller.
# This includes NGINX (which also creates the 'nginx' user), Python,
# the SELinux policy development tools, make, and firewalld.
RUN dnf install -y \
nginx \
python3 \
selinux-policy-devel \
make \
firewalld \
&& dnf clean all
# --- 2. Copy Application Code and Set Ownership ---
# Set the working directory and copy the application files.
# The --chown flag ensures all copied files are immediately owned by the 'nginx' user,
# which was created during the nginx package installation.
WORKDIR /app
COPY --chown=nginx:nginx app/myproject /app/myproject
COPY --chown=nginx:nginx app/requirements.txt /app/requirements.txt
# --- 3. Install Python Dependencies in a Virtual Environment ---
# Switch to the non-root user to create and populate the virtual environment.
# This ensures that the venv is also owned by the correct user.
USER nginx
RUN python3 -m venv django-venv
# Note: The ENV PATH is set for subsequent RUN commands under this USER.
ENV PATH="/app/django-venv/bin:$PATH"
RUN pip install --no-cache-dir -r requirements.txt
# --- 4. Configure SELinux Policy ---
# Switch back to root to make system-level changes.
USER root
# This is the most critical step for bootc images.
# We define the SELinux policy rules here. These rules will persist on the
# final bootable system even if the file labels themselves are reset.
RUN semanage fcontext -a -t httpd_sys_content_t "/app(/.*)?" && \
semanage fcontext -a -t httpd_exec_t "/app/django-venv/bin(/.*)?"
# --- 5. Create and Install a Custom SELinux Module ---
# This module grants systemd (init_t) permission to read the symbolic links
# in our venv that are labeled as httpd_exec_t.
RUN echo "module gunicorn_init 1.0; \
require { \
type init_t; \
type httpd_exec_t; \
class lnk_file read; \
} \
allow init_t httpd_exec_t:lnk_file read;" > /tmp/gunicorn_init.te && \
cd /tmp && \
make -f /usr/share/selinux/devel/Makefile gunicorn_init.pp && \
semodule -i gunicorn_init.pp
# --- 6. Configure System Services ---
# Copy the service and configuration files into the image.
COPY django.service /etc/systemd/system/
COPY nginx-myproject.conf /etc/nginx/conf.d/myproject.conf
# Enable all the services that should start on boot.
RUN systemctl enable firewalld nginx.service django.service
# --- 7. Configure Firewall ---
# Use the offline command to open the HTTP port in the firewall for the final image.
RUN firewall-offline-cmd --add-port=80/tcp
Now that you have your configuration files, let’s move on to the next lesson, where we’ll create the bootable RHEL image mode container.