Skip to main content
Redhat Developers  Logo
  • Products

    Platforms

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat AI
      Red Hat AI
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • See all Red Hat products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat Developer Hub
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat OpenShift Local
    • Red Hat Developer Sandbox

      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Red Hat OpenShift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • See all technologies
    • Programming languages & frameworks

      • Java
      • Python
      • JavaScript
    • System design & architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer experience

      • Productivity
      • Tools
      • GitOps
    • Automated data processing

      • AI/ML
      • Data science
      • Apache Kafka on Kubernetes
    • Platform engineering

      • DevOps
      • DevSecOps
      • Red Hat Ansible Automation Platform for applications and services
    • Secure development & architectures

      • Security
      • Secure coding
  • Learn

    Featured

    • Kubernetes & cloud native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • AI/ML
      AI/ML Icon
    • See all learning resources

    E-books

    • GitOps cookbook
    • Podman in action
    • Kubernetes operators
    • The path to GitOps
    • See all e-books

    Cheat sheets

    • Linux commands
    • Bash commands
    • Git
    • systemd commands
    • See all cheat sheets

    Documentation

    • Product documentation
    • API catalog
    • Legacy documentation
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore the Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Build trusted Python containers with Project Hummingbird and Calunga

Reducing supply chain risk for containerized Python applications

May 5, 2026
Fernando Lozano
Related topics:
Hardened imagesContainersLinuxPythonSecurity
Related products:
Podman DesktopRed Hat Advanced Developer SuiteRed Hat Enterprise Linux

    Project Hummingbird and Calunga are open source projects that offer the beginnings of a secure software supply chain. These projects help open source and commercial developers build application containers with confidence that they are not shipping malicious content.

    This article describes how to use container images produced by Project Hummingbird with libraries produced by the Calunga project to build application containers free of code from unknown origins. You can use these tools to deliver more secure container images for your applications.

    Red Hat supports container images produced by Project Hummingbird as part of Red Hat Hardened Images. Red Hat also supports libraries from the Calunga project through the trusted libraries feature in Red Hat Advanced Developer Suite. You can redistribute container images that you build using these tools and run them on any platform that supports the Open Container Initiative (OCI) standard—for example, on Ubuntu Linux, Windows, and macOS computers running Podman or Docker.

    You are not required to buy a Red Hat product to use Red Hat Hardened Images and trusted libraries, or to redistribute the containers you build. However, if you have valid subscriptions for Red Hat products such as Red Hat Enterprise Linux, Red Hat OpenShift, or Red Hat Advanced Developer Suite, you receive support for your base images and libraries under your existing support coverage.

    Prepare your developer environment

    This is the second part of a series about using container images from Project Hummingbird. Part 1 describes how to use Project Hummingbird to run databases and web servers and explores its distroless nature. You can understand this article without reading the first part. This installment focuses on using runtime and builder images from Project Hummingbird for different programming languages, using Python as an example.

    You can find the code for this article on GitHub. I tested these commands on my Fedora Linux laptop using the podman command in rootless mode. You can easily adapt these steps for other Linux distributions, Windows, macOS, and Docker.

    If you prefer a graphical interface over a command shell, try Podman Desktop. It runs on all major operating systems and includes the Podman container engine.

    A security-focused foundation for your supply chain

    Like it or not, applications are often the most vulnerable part of an organization's information security chain. In the old times, developers focused primarily on security vulnerabilities in dependencies, such as libraries prone to buffer overflow issues. Updating the copy of the library included within an application container would usually resolve the issue. This process is similar to updating an operating system's libraries and binaries to fix issues at the server or workstation machine level.

    Monitoring for Common Vulnerabilities and Exposures (CVEs) and patches from your preferred libraries is no longer enough. Malicious actors now target the communities that produce those libraries and the public infrastructure that distributes them. You might download a library that someone has tampered with. This can happen whether the compromised copy comes from an official community repository or a public mirror. The file could even come from your internal corporate servers. When you include that compromised code in your application, it can harm your organization, your customers, and your users.

    Red Hat Hardened Images provides base container images, while trusted libraries provides trusted dependency libraries. Red Hat builds both using a strict, security-focused software development lifecycle. These artifacts meet SLSA build level 3 standards and offer fast resolution times for known CVEs by closely following upstream repositories. Red Hat hosts these artifacts in a monitored, security-focused content distribution network.

    A Python example

    All sample code for this article is available in a public GitHub repository. To follow along, clone the repository and navigate into the directory.

    $ git clone https://github.com/flozanorht/python-hello.git

    The repository contains two directories. Start in the single-stage directory; we will switch to the multi-stage directory later.

    $ cd python-hello/single-stage

    This very simple Python application uses the Flask framework. It returns the version of the Python runtime and the Flask framework as a JSON response. This example is not production-ready because it enables debugging and does not use a production-grade web server.

    Listing 1: app/app.py 

    import sys
    import flask
    from flask import Flask, jsonify
    app = Flask(__name__)
    @app.route('/')
    def hello():
        return jsonify({
            "python_version": sys.version.split()[0],
            "flask_version": flask.__version__
        })
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=5000, debug=True)

    The following Containerfile packages the application as a container image using public repositories. This Containerfile is representative of recommended practices for Python containers:

    • It uses the slim container image variant, which provides a smaller runtime for applications that do not require native code libraries.
    • It runs the application as a non-root user to improve security.

    Some developers might argue that there's no reason to use Python virtual environments in a container, but you will also find many others favor their use. In this case, a virtual environment simplifies the transition to multi-stage builders later in this article.

    Listing 2: Containerfile.dockerhub

    FROM python:3.12-slim
    RUN python -m venv /opt/venv
    ENV PATH="/opt/venv/bin:$PATH" \
        PYTHONUNBUFFERED=1 \
        PYTHONDONTWRITEBYTECODE=1
    WORKDIR /app
    COPY app/requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt 
    RUN useradd -m -u 1000 appuser && \
        chown -R appuser:appuser /app
    USER appuser
    COPY app .
    EXPOSE 5000
    CMD ["python", "app.py"]

    Build the container image using Podman:

    $ podman build --no-cache -t flask-app -f Containerfile.dockerhub

    Start a test container using your new image:

    $ podman run -d -p 127.0.0.1:5000:5000 --name my-flask-app flask-app

    Finally, test your application container using a web browser or a curl command:

    $ curl 127.0.0.1:5000

    The output should look similar to this:

    {
      "flask_version": "3.1.3",
      "python_version": "3.12.13"
    }

    When you're done, stop and delete your test container. You will reuse this container name to test the different variations of your application container image.

    $ podman stop my-flask-app ; podman rm my-flask-app

    Using base images from Project Hummingbird

    Adapting the previous example for Project Hummingbird requires more than replacing the name of the base image in the FROM instruction. While Project Hummingbird maintains compatibility with popular community images, it also minimizes and hardens its images. These security-focused optimizations might require changes to your Containerfile. For example:

    • Use the array syntax for RUN instructions. Project Hummingbird images usually do not include a shell, which the string syntax requires.
    • Switch to the root user (USER 0) before performing operation that require elevated privileges. Project Hummingbird images do not run as root by default.

    We also recommend following these conventions:

    • Follow Fedora Linux conventions for paths and command names—for example, use python3 for the Python interpreter.
    • Use Project Hummingbird conventions for environment labels, ports, variables, users, and paths. For example, use the CONTAINER_DEFAULT_USER variable, which the base images set as the default user.

    Note

    Using a builder variant with a multi-stage build is often more effective than single-stage builds with the regular variant. We will demonstrate this later in the article.

    Listing 3: single-stage/Containerfile.hummingbird

    FROM registry.access.redhat.com/hi/python:3.12
    USER 0
    RUN [ "python3", "-m", "venv", "/opt/venv" ]
    ENV PATH="/opt/venv/bin:$PATH" \
        PYTHONUNBUFFERED=1 \
        PYTHONDONTWRITEBYTECODE=1
    WORKDIR /app
    COPY app/requirements.txt .
    RUN [ "pip3", "install", "--no-cache-dir", "-r", "requirements.txt" ]
    USER ${CONTAINER_DEFAULT_USER}
    COPY app .
    EXPOSE 5000
    CMD ["python3", "app.py"]

    To build and test your Project Hummingbird application container, use the same commands as the previous example but replace the Containerfile name.

    $ podman build --no-cache -t flask-app -f Containerfile.hummingbird
    $ podman run -d -p 127.0.0.1:5000:5000 --name my-flask-app flask-app
    $ curl 127.0.0.1:5000

    When you're satisfied with your tests, stop and delete your application container. You will reuse this container name for other versions of the application image.

    $ podman stop my-flask-app ; podman rm my-flask-app

    Using trusted libraries

    Building an application container from a secure base image while adding unverified dependencies can result in a vulnerable container. Let's modify the previous example to use dependencies from the trusted libraries index.

    The only difference between Containerfile.hummingbird and Containerfile.calunga is the use of a mount secret in the RUN instruction that runs pip:

    RUN --mount=type=secret,id=pip.conf,target=/opt/venv/pip.conf [ "pip3", "install", "--no-cache-dir", "-r", "requirements.txt" ]

    That secret provides a configuration for pip to use the trusted libraries component. But first, you must create the configuration file that contains your personal authentication credentials.

    You need a valid Red Hat account. You can use an account with a no-cost Red Hat Developer Subscription for Individuals. We recommend that you create a service account to avoid exposing your credentials in pip configuration files or CI/CD pipeline settings.

    Log in to Red Hat Developer or register for an account. Then, access the Red Hat Customer Portal to create a service account. You might need to log in to the Customer Portal again using your Red Hat Developer account.

    In your local repository clone, copy the template file pip.conf.orig to pip.conf. Edit the file to include the username and the very long password for your service account. Then build your container image using the pip.conf file as a secret mounted into the build container.

    $ podman build --no-cache -t flask-app --secret id=pip.conf,src=pip.conf -f Containerfile.calunga

    As your build progresses, notice that it downloads Python wheels from packages.redhat.com. The build command should produce output similar to the following snippet:

    ...
    STEP 7/11: RUN --mount=type=secret,id=pip.conf,target=/opt/venv/pip.conf [ "pip3", "install", "--no-cache-dir", "-r", "requirements.txt" ]
    Looking in indexes: https://5346907%7Crhtl:****@packages.redhat.com/trusted-libraries/python/
    Collecting Flask>=3.0.0 (from -r requirements.txt (line 1))
      Downloading https://packages.redhat.com/api/pulp-content/public-trusted-libraries/main/flask-3.1.3-0-py3-none-any.whl.metadata (3.2 kB)
    …

    You will see similar “Downloading” messages for each application dependency.

    If all went well, test your new container image:

    $ podman run -d -p 127.0.0.1:5000:5000 --name my-flask-app flask-app
    $ curl 127.0.0.1:5000

    While this version of your application container is still running, you can verify that it does not contain a pip.conf file. This confirms that the image does not record your authentication credentials.

    $ podman export my-flask-app | tar t | grep pip.conf

    As with the previous attempts, stop and delete your application container.

    $ podman stop my-flask-app ; podman rm my-flask-app

    A multi-stage example

    Many developers prefer using multi-stage builds for several reasons:

    • Adding build-time dependencies, such as development headers and a C compiler, to interface with processor native code.
    • Providing secrets to the build as environment variables. This prevents the secrets from being recorded in the final application image layers.
    • Reducing the size of the final application image by using a base image that excludes build-time dependencies and other conveniences that are not necessary to run the application.

    Although our simple application does not strictly require a multi-stage build, the following example serves as a template for applications that do.

    When using Docker Hub images, a multi-stage build uses the standard Python image for the first stage and the slim variant for the second stage.

    In this case, using a Python virtual environment simplifies copying application dependencies between stages.

    Listing 3: multi-stage/Containerfile.dockerhub

    FROM docker.io/library/python:3.12 AS builder
    # Install non-Python dependencies and additional build-time tools
    WORKDIR /app
    RUN python -m venv /opt/venv
    ENV PATH="/opt/venv/bin:$PATH"
    COPY app/requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt 
    FROM docker.io/library/python:3.12-slim
    WORKDIR /app
    COPY --from=builder /opt/venv /opt/venv
    ENV PATH="/opt/venv/bin:$PATH" \
        PYTHONUNBUFFERED=1 \
        PYTHONDONTWRITEBYTECODE=1
    RUN useradd -m -u 1000 appuser && \
        chown -R appuser:appuser /app
    USER appuser
    COPY app .
    EXPOSE 5000
    CMD ["python", "app.py"]

    Building and testing the application container is basically the same as the single-stage process. Be sure to navigate to the multi-stage directory.

    $ cd ../multi-stage
    $ podman build --no-cache -t flask-app -f Containerfile.dockerhub
    $ podman run -d -p 127.0.0.1:5000:5000 --name my-flask-app flask-app
    $ curl 127.0.0.1:5000
    $ podman stop my-flask-app ; podman rm my-flask-app

    For Project Hummingbird, use the builder image variant for the first stage and the regular image for the second stage. The following example also uses the Calunga index during the first stage.

    Listing 4: multi-stage/Containerfile.calunga

    FROM registry.access.redhat.com/hi/python:3.12-builder AS builder
    USER 0
    # Install non-Python dependencies and additional build-time tools
    RUN python3 -m venv /opt/venv
    ENV PATH="/opt/venv/bin:$PATH"
    WORKDIR /app
    COPY app/requirements.txt .
    RUN --mount=type=secret,id=pip.conf,target=/opt/venv/pip.conf pip3 install --no-cache-dir -r requirements.txt
    USER ${CONTAINER_DEFAULT_USER}
    FROM registry.access.redhat.com/hi/python:3.12
    USER 0
    COPY --from=builder /opt/venv /opt/venv
    ENV PATH="/opt/venv/bin:$PATH" \
        PYTHONUNBUFFERED=1 \
        PYTHONDONTWRITEBYTECODE=1
    USER ${CONTAINER_DEFAULT_USER}
    WORKDIR /app
    COPY app .
    EXPOSE 5000
    CMD ["python3", "app.py"]

    You can use the string syntax for RUN instructions in the first stage because Project Hummingbird builder images include a shell. This convenience makes multi-stage builds a popular choice for developers using Project Hummingbird.

    To build and test this image, copy the pip.conf file from the previous example using trusted libraries. Then, run the podman and curl commands:

    $ cp ../single-stage/pip.conf .
    $ podman build --no-cache -t flask-app --secret id=pip.conf,src=pip.conf -f Containerfile.calunga
    $ podman run -d -p 127.0.0.1:5000:5000 --name my-flask-app flask-app
    $ curl 127.0.0.1:5000
    $ podman stop my-flask-app ; podman rm my-flask-app

    Continuing your security journey

    Using secure base images and trusted dependency libraries is an important first step for your software supply chain. This is a straightforward step that does not require specialized tools like security scanners or sophisticated processes to ensure the provenance and attestation of software artifacts.

    The trusted libraries component currently offers dependencies for Python only. We plan to expand coverage to other programming language runtimes that Project Hummingbird serves, such as Node.js and Java.

    Project Hummingbird and trusted libraries do not prevent vulnerabilities in your own application code. These tools also do not prevent tampering with application container images stored on internal and public container registries.

    The open source project Konflux provides many of the upstream components of Red Hat Advanced Developer Suite. It includes the tools required to implement a security-focused software development lifecycle that creates and signs SBOMs and container images while running security scanners.

    Many thanks to Luiz Carvalho and Shane Boulden for their review of this article.

    Related Posts

    • Exploring distroless containers with Project Hummingbird

    • Build .NET container images with Tekton

    • Bootable containers: Reduce friction with Red Hat Enterprise Linux image mode

    • Unlocking UBI to Red Hat Enterprise Linux container images

    • Building Red Hat MCP-ready images with image mode for Red Hat Enterprise Linux

    • How I used Red Hat Lightspeed image builder to create CIS (and more) compliant images

    Recent Posts

    • Build trusted Python containers with Project Hummingbird and Calunga

    • Simplify distributed tracing: ObservabilityInstaller installation

    • Storage isolation with OpenStack Services on OpenShift distributed zones

    • Run multiple OpenStack Services on OpenShift with HCPs

    • Kafka Monthly Digest: April 2026

    What’s up next?

    Learning Path Featured image for Red Hat Enterprise Linux.

    Build a bootable Python Django app with image mode and Podman

    Use Podman Desktop to create a bootable Django-based application using image...
    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer tools
    • Interactive tutorials
    • API catalog

    Quicklinks

    • Learning resources
    • E-books
    • Cheat sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site status dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2026 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility