Universal Base Image

If you're like me—a developer who works with customers who rely on the tried-and-true Red Hat Enterprise Linux (RHEL), works with containerized applications, and also prefers to work with Fedora Linux as their desktop operating system—you're excited by the announcement of the Universal Base Images (UBI). This article shows how UBI actually works, by building the container image for a simple PHP application.

With UBI, you can build and redistribute container images based on Red Hat Enterprise Linux without requiring a Red Hat subscription. Users of UBI-based container images do not need Red Hat subscriptions. No more extra work creating CentOS-based container images for your community projects or for your customers that prefer self-support.

I tested all these steps on my personal Fedora 29 system, and they should work on any Linux distribution. I am also a big fan of the new container tools such as Podman, which should be available to your favorite Linux distribution. If you are working on a Windows or MacOS system, you can replace the Podman commands with Docker.

Working with Red Hat's public registry

The download instructions at the Container Catalog assume the usage of the Red Hat's terms-based registry, which is a private registry. The instructions on this post make the same assumption. If your intent is building only freely redistributable UBI-based images, you can opt to use the Red Hat's public registry instead of the Red Hat's private registry. To do this:

  • Skip the steps that register at the Red Hat Developers web site, create a service account, and log in to the Red Hat Container Registry.
  • Replace the private registry host name registry.redhat.io with the public registry host name registry.access.redhat.com in all Podman commands.
  • Use the public registry host name registry.access.redhat.com in the FROM directives from your Dockerfiles.

Downloading a UBI container image

Start by visiting the Red Hat Container Catalog and searching for UBI. Among the first results, you'll find "ubi7/ubi - Red Hat Universal Base Image 7 by Red Hat, Inc.," which I'll use as my base image because I have yet to upskill to the new RHEL8 release. Notice, however, that there are already RHEL8-based UBI images for you to use. With those, you can experiment with the new RHEL8 release without installing a full-blown OS in a virtual machine.

The Container Catalog page for ubi7/ubi states that: "This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products." Not all UBI container images are freely redistributable, so be warned. Using UBI, you or your users could decide at any time to become Red Hat customers, which entitles you to Red Hat's support services without rebuilding your container images or redeploying your applications to become RHEL-based. That's something you could not do as easily using a CentOS-based container image.

If you click Get This Image, you'll see that you need authentication to download UBI images from Red Hat. If you're not already registered at the Red Hat Developers web site, note that it is free, and it provides you free access to lots of Red Hat technologies.

After you register and log in to the Red Hat Developers, visit the Red Hat Customer's Portal. There you generate a service account that allows you to download Red Hat container images. Next, cut and paste the service account name and its auto-generated token to log in to the Red Hat Container Registry:

$ sudo podman login -u "your service account name" -p "your very long token" registry.redhat.io

Now you can fire up your first UBI-based container:

$ sudo podman run -it registry.redhat.io/ubi7/ubi bash
[root@8285feb36cdb /]#

Exploring the UBI container image

The previous command starts a Bash shell in a container that provides a basic Red Hat Enterprise Linux OS. Your application container image probably requires that you add a few packages, for example, a programming language runtime. Before UBI, you would require a Red Hat subscription to download RHEL packages. Now UBI comes with its own set of package repositories with freely redistributable content. These repositories provide a subset of RHEL packages. Let's look at these package repositories:

[root@8285feb36cdb /]# yum repolist
Loaded plugins: ovl, product-id, search-disabled-repos, subscription-manager
This system is not registered with an entitlement server. You can use subscription-manager to register.
repo id                                   repo name                                                                                         status
ubi-7/x86_64                              Red Hat Universal Base Image 7 Server (RPMs)                                                      832
ubi-7-optional/x86_64                     Red Hat Universal Base Image 7 Server - Optional (RPMs)                                            18
ubi-7-rhah/x86_64                         Red Hat Universal Base Image Atomic Host (RPMs)                                                     3
ubi-7-rhscl/x86_64                        Red Hat Software Collections for Red Hat Universal Base Image 7 Server (RPMs)                     320
repolist: 1173

Note that a subset of the Red Hat Software Collections for RHEL7 is available among the UBI repositories. This provides you with the latest supported releases of PHP, Node.js, and other programming language runtimes.

If you're a Java developer, note that UBI provides both new and old releases OpenJDK:

[root@8285feb36cdb /]# yum search openjdk

The UBI images provide access to all of RHEL when the host system is registered with a valid Red Hat subscription. It is only when your host system is not registered that you're limited to the UBI repositories:

[root@8285feb36cdb /]# ls /etc/yum.repos.d/
redhat.repo  ubi.repo

If you are curious about the UBI repositories, check the ubi.repo configuration file:

[root@8285feb36cdb /]# vi /etc/yum.repos.d/ubi.repo

We are done exploring the base UBI container image, and you can now exit your text editor and your images' shell.

Exploring the UBI minimal container image

Note that the ubi container image includes some tools you may not need on your application container image, such as the vim text editor. Just switch to the ubi7/ubi-minimal container image if you think you don't need those extra commands. Having these extra tools may be helpful during development of your container images, however.

Fire up a new container running the ubi7/ubi-minimal container image. You'll notice it displays a different default Bash prompt:

$ sudo podman run -it registry.redhat.io/ubi7/ubi-minimal bash

This is not one of those busybox-based minimal container images. It is a real RHEL container image, made from real RHEL packages. These packages are hardened and updated the same way for UBI and for subscription customers.

The main difference between the ubi and ubi-minimal images is that the first provides the full yum toolset. Yum adds some dependencies such as Python packages. In contrast, the second provides microdnf as a replacement. The microdnf tool works from the same repositories as Yum, but only provides the ability to install, update, and delete packages:

bash-4.2# ls /etc/yum.repos.d/
redhat.repo  ubi.repo
bash-4.2# microdnf --help
 microdnf [OPTION?] COMMAND

 clean - Remove cached data
 install - Install packages
 remove - Remove packages
 update - Update packages

We are done exploring the minimal UBI container image. You can now exit your images' shell. Now let's see how a real application image looks like using UBI.

A sample application using UBI

My test application is available from GitHub, if you do not want to create its files and type its contents by yourself. It consists of only two files: a Dockerfile and an index.php script.

To start, you can create a work folder and, inside that, create an inspired "Hello, world" PHP-enhanced web page, for example:

<?php print "Hello, world!\n" ?>

In the same work folder, create a Dockerfile. The Dockerfile installs Apache HTTPd and PHP from the Software Collections Library, using the UBI package repositories:

FROM registry.redhat.io/ubi7/ubi

RUN yum -y install --disableplugin=subscription-manager \
  httpd24 rh-php72 rh-php72-php \
  && yum --disableplugin=subscription-manager clean all

ADD index.php /opt/rh/httpd24/root/var/www/html

RUN sed -i 's/Listen 80/Listen 8080/' \
  /opt/rh/httpd24/root/etc/httpd/conf/httpd.conf \
  && chgrp -R 0 /var/log/httpd24 /opt/rh/httpd24/root/var/run/httpd \
  && chmod -R g=u /var/log/httpd24 /opt/rh/httpd24/root/var/run/httpd


USER 1001

CMD scl enable httpd24 rh-php72 -- httpd -D FOREGROUND

The --disableplugin=subscription-manager option avoids warning messages when building from a machine that has no active subscription, such as my personal Fedora machine.

Note that I took care of creating a Red Hat OpenShift-friendly container image. That image works without root privileges and under a random userid. This is something everyone should do, regardless of their target container runtime. Unlike the container engine from your desktop OS, and some Kubernetes distributions, Red Hat OpenShift by default refuses to run containers that require elevated privileges.

Enter your work folder and use Podman to build the container image:

$ sudo podman build -t php-ubi .

This is cool, isn't it? Building a RHEL-based container image from a non-RHEL system, without a RHEL subscription, and without having to fuss with Yum repositories!

Now start a container from your new container image:

$ sudo podman run --name hello -p 8080:8080 -d localhost/php-ubi

And test your container using curl:

$ curl localhost:8080
Hello, world!

You can now publish your container image at a public container registry, such as Quay.io. Red Hat allows you to do so when use UBI.

After you create the php-ubi repository on your Quay.io personal account, you will be able to do:

$ sudo podman login -u "youraccount" quay.io
Login Succeeded!
$ sudo podman tag localhost/php-ubi quay.io/youraccount/php-ubi
$ sudo podman push quay.io/youraccount/php-ubi
Writing manifest to image destination
Storing signatures

You can find mine at quay.io/flozanorht/php-ubi.

How minimal is minimal?

As an experiment, let's change our Dockerfile to use the ubi-minimal container image. The following listing highlights these changes:

FROM registry.redhat.io/ubi7/ubi-minimal

RUN microdnf -y install --nodocs \
  httpd24 rh-php72 rh-php72-php \
  && microdnf clean all

ADD index.php /opt/rh/httpd24/root/var/www/html

RUN sed -i 's/Listen 80/Listen 8080/' \
  /opt/rh/httpd24/root/etc/httpd/conf/httpd.conf \
  && chgrp -R 0 /var/log/httpd24 /opt/rh/httpd24/root/var/run/httpd \
  && chmod -R g=u /var/log/httpd24 /opt/rh/httpd24/root/var/run/httpd


USER 1001

CMD scl enable httpd24 rh-php72 -- httpd -D FOREGROUND

Build a new container image and compare its size to the previous one:

$ sudo podman build -t php-ubi-minimal .
$ sudo podman images
REPOSITORY                           TAG     IMAGE ID       CREATED          SIZE
localhost/php-ubi-minimal            latest  ab3c0bd38e3f   22 seconds ago   270 MB
localhost/php-ubi                    latest  5407a95fdd01   30 minutes ago   280 MB
registry.redhat.io/ubi7/ubi-minimal  latest  8d0998e077d3   4 weeks ago       83 MB
registry.redhat.io/ubi7/ubi          latest  c096c0dc7247   4 weeks ago      214 MB

See that the ubi and ubi-minimal have quite a difference in size (214MB versus 83MB), but this difference does not translate to our PHP application image (280MB versus 270MB). The difference is caused by all the dependencies required by Apache HTTPd, PHP, and the SCL. I could trim my image by carefully selecting which packages to install, but, in the end, the choice of UBI may not make a huge difference for your application.

Lesson learned: there are more factors than base image size that make up your application image smaller or larger. I'd rather start from a tried-and-true RHEL-based UBI image than work with some other distro base image. Would that other base image provide bug and security fixes as well as backports of those fixes, like Red Hat does?

What about UBI8?

Changing my Dockerfile to use a RHEL8-based UBI was not hard. RHEL8 uses AppStreams instead of SCL, and its PHP implementation provides only FastCGI. That means I need to start the php-fpm daemon before starting Apache HTTPd.

Here's the complete Dockerfile for ubi8:

FROM registry.redhat.io/ubi8/ubi

RUN yum --disableplugin=subscription-manager -y module enable \
  php:7.2 \
  && yum --disableplugin=subscription-manager -y install \
  httpd php \
  && yum --disableplugin=subscription-manager clean all

ADD index.php /var/www/html

RUN sed -i 's/Listen 80/Listen 8080/' /etc/httpd/conf/httpd.conf \
  && mkdir /run/php-fpm \
  && chgrp -R 0 /var/log/httpd /var/run/httpd /run/php-fpm \
  && chmod -R g=u /var/log/httpd /var/run/httpd /run/php-fpm


USER 1001

CMD php-fpm & httpd -D FOREGROUND

You can test this Dockerfile using the same Podman commands above. My project in GitHub also provides an ubi8-minimal variation that I am sure you could create by yourself.

For the curious, here is the difference in size between standard and minimal ubi8 images:

$ sudo podman images
REPOSITORY                            TAG     IMAGE ID       CREATED         SIZE
localhost/php-ubi-minimal             latest  ab87af70662a   2 minutes ago   181 MB
localhost/php-ubi                     latest  aed784c69302   12 minutes ago  264 MB
registry.redhat.io/ubi8/ubi           latest  4a0518848c7a   3 weeks ago     216 MB
registry.redhat.io/ubi8/ubi-minimal   latest  3bfa511b67f8   3 weeks ago      91.7 MB

The difference in size for my application images is much greater using ubi8 parent images. This shows that AppStreams package is more efficient than SCL packaging. Although the ubi8 container images are a little bigger than the ubi7 ones, the final minimal application image is considerably smaller.

Learn More

Update on June 13: Added instructions explaining the use of registry.access.redhat.com (public registry) as al alternative for registry.redhat.io (private registry).

Last updated: November 1, 2023