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 ... bash-4.2#
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 Usage: microdnf [OPTION?] COMMAND Commands: 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:
<html> <body> <?php print "Hello, world!\n" ?> </body> </html>
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 EXPOSE 8080 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 <html> <body> Hello, world! </body> </html>
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 Password: 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 EXPOSE 8080 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 EXPOSE 8080 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.
- Sample Code on GitHub
- Red Hat Container Catalog
- Red Hat Software Collections
- Introducing the Red Hat Universal Base Image
- All UBI Images on the Red Hat Container Catalog
- Introducing Application Streams in RHEL 8
- Podman and Buildah for Docker
- Podman Basics Cheat Sheet
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: September 23, 2019