Bookmark this page

Chapter 3.  Building and Publishing Container Images

Abstract

Goal

Build, deploy, and manage the lifecycle of container images by using a container registry.

Objectives
  • Build Container Images for Red Hat OpenShift by using Red Hat Universal Base Images (UBI).

  • Use container images from remote container registries in Red Hat OpenShift.

  • Create and manage Red Hat OpenShift Image Streams.

Sections
  • Building Container Images for Red Hat OpenShift (and Guided Exercise)

  • Using External Registries in Red Hat OpenShift (and Guided Exercise)

  • Creating Image Streams (and Guided Exercise)

Lab
  • Building and Publishing Container Images

Building Container Images for Red Hat OpenShift

Objectives

  • Build Container Images for Red Hat OpenShift by using Red Hat Universal Base Images (UBI).

Container Images for Red Hat OpenShift

Containers are portable. You can build a container image once, and then use it to run your application anywhere: laptops, virtual machines, bare-metal servers, or cloud platforms. Portability, however, is not guaranteed in every scenario. Factors such as a cautious security policy or a nonstandard system architecture might prevent your containers from running on certain environments.

You can optimize your container images for Red Hat OpenShift by using a set of guidelines. These guidelines include container best practices, such as the use of reliable base images, or removing temporary files. See the Red Hat OpenShift Development I: Introduction to Containers with Podman course for a comprehensive list of best practices for building your container images. This lecture focuses on the guidelines that optimize container images for Kubernetes and OpenShift.

Use Red Hat Universal Base Images

When defining custom container images, Red Hat recommends the use of Red Hat Universal Base Images (UBI) as the base container images for your applications. UBI images are certified, tested, and regularly maintained images that Red Hat provides at no cost. UBI images also provide the following major benefits:

Universal

Designed to be used as the base images for developing container-based applications.

Robust

UBI images are based on Red Hat Enterprise Linux (RHEL). This characteristic brings aspects of RHEL, such as stability and security vulnerabilities management, to your base container images.

Standard

UBI images are compliant with the Open Container Initiative (OCI).

Extensible

UBI images provide package managers for installing additional software.

OpenShift-optimized

Tailored to work seamlessly on Red Hat OpenShift.

Redistributable

The UBI End User Licensing Agreement (EULA) permits free distribution of the applications that you build on top of UBI images.

Red Hat provides four types of UBI images, designed to cover most use cases.

Image typeImage nameDescription
Standard ubi For most of the applications and use cases.
Init ubi-init For containers that run multiple systemd services.
Minimal ubi-minimal Smaller image for applications that manage their own dependencies and depend on fewer OS components.
Micro ubi-micro Smallest image for optimized memory-footprint use cases. For applications that use almost no OS components.

For more details about each type, see the references section.

Runtime UBI Images For Developers

As well as the four main UBI images, Red Hat provides specific UBI images for popular runtimes. These images are built on top of the main UBI images and provide the same benefits. They include the common components and tools of each runtime, and are ready for developers to add and run their code. Red Hat provides UBI images for the following runtime languages:

  • OpenJDK

  • Node.js

  • Python

  • PHP

  • .NET

  • Go

  • Ruby

For each runtime, Red Hat provides images for each supported major version of the runtime. For example, for Node.js 18, Red Hat provides the ubi9/nodejs-18 image, which is indirectly based on the standard ubi9/ubi image, and ubi9/nodejs-18-minimal, which is based on the minimal ubi9/ubi-minimal application.

Note

Use UBI runtime images when possible. Relying on a UBI runtime image is faster and more efficient than using the base UBI images, because you do not have to install the runtime components required to run your application.

Optimize Containerfiles for OpenShift

Start by using a UBI image in the FROM instruction of your Containerfile. The UBI images are available at the registry.access.redhat.com public container registry. The image name format is as follows:

registry.access.redhat.com/NAMESPACE/NAME[:TAG]

The NAMESPACE part specifies the UBI version that you use, such as ubi8 or ubi9. Red Hat matches the major version of UBI images to the major version of RHEL. Therefore, UBI 9 images derive from RHEL 9, and UBI 8 images derive from RHEL 8. For example, if you want to create an image for a Node.js 18 application, and use a minimal UBI image, then you can use this instruction:

FROM registry.access.redhat.com/ubi9/nodejs-18-minimal:1-56

Note that the previous example uses a specific tag. This is a good practice that prevents potential breaking updates, which might happen if you use the latest tag or no tag at all.

Note

You can browse available UBI images at the Certified container images page in the Red Hat Ecosystem Catalog.

Use UBI-specific Instructions

To create your own images from a runtime UBI image, follow the instructions of the specific UBI image. You can find the instructions of each UBI image in the Red Hat Ecosystem Catalog, by navigating to the image page and clicking the Overview tab.

Figure 3.1: The Overview tab of the Red Hat Ecosystem Catalog provides instructions to use an image

Most of the UBI runtime images support two Containerfile build approaches, which are typically documented in the instructions of each image.

Source-to-Image (S2I) Scripts

Many UBI images are designed to work with the Source-to-Image (S2I) build strategy in Red Hat OpenShift. This strategy is an OpenShift feature that you can use to build container images in OpenShift by providing the source code of the application. The UBI images that support S2I include a set of scripts called S2I scripts. These scripts rely on popular conventions and tools for each runtime, and take care of installing the application dependencies, building, and running the application, to produce an OpenShift-optimized image.

Note

The OpenShift build capabilities are explained later in the course.

Custom Build

You can ignore the S2I scripts if they do not suit the needs or your application. For example, some environments or applications might require nonstandard build processes. You can use UBI images as the base images in those cases. Red Hat recommends that you use the following practices to produce an OpenShift-optimized image.

Add Support for Arbitrary Non-root Users

Red Hat OpenShift, by default, does not use the USER instruction set by a container image. For security reasons, OpenShift uses a random user ID other than the root user ID (0) to run containers. Additionally, OpenShift makes this random user a member of the root group, which corresponds to the 0 group ID.

This approach mitigates the risk of processes running in the container getting escalated privileges on the cluster nodes due to security vulnerabilities in the container engine. When you create or update the Containerfile of an image that runs on OpenShift, address the following aspects:

  • The root group (group ID 0) must own the directories and files where the processes that run in the container read or write.

  • The root group must have permissions to write to and read from these directories and files.

  • The root group must have execute permissions to run the application executable binaries.

  • The processes running in the container must not listen on privileged ports, namely ports below 1024, because these processes do not run as privileged users.

Adding the following RUN instruction to your Containerfile recursively sets the permissions of a directory to allow users in the root group to access this directory and its contents in the container.

RUN chgrp -R 0 directory && \ 1
    chmod -R g=u directory 2

1

The chgrp command changes the group ownership of directory to the root group (group ID 0). The arbitrary user account that runs the container on OpenShift is always a member of the root group.

2

Make the group permissions equal to the owner user permissions, which by default are read and write. You can use the g+rwX argument with the same results.

Note

The S2I scripts included in UBI images typically take care of adjusting the permissions to meet the preceding specifications.

Ensure That Your Containers Handle Interruption Signals

OpenShift expects application instances to shut down gracefully before the cluster removes the instances from the load balancer. Applications must ensure they gracefully terminate user connections before they exit.

OpenShift sends a SIGTERM signal to the processes in the container to terminate an application.

The application might require additional procedures during shutdown, for example to wait until all open connections are closed, release resources, or gracefully terminate individual connections at the next opportunity. In such cases, you might have to implement additional handling in your application.

If the application uses a complex procedure to start, then developers commonly encapsulate the logic in the entrypoint script. In such cases, the entrypoint script is responsible for passing the SIGTERM signal to the application, for example:

#!/bin/env bash

function graceful_shutdown() {
  kill -SIGTERM "$java_pid"
  wait "$java_pid"
  exit 0
}

# Trap the SIGTERM signal
trap graceful_shutdown SIGTERM

...script omitted...

# Start the application
java -jar example.jar &
java_pid=$!

...script omitted...

# Wait for the process to finish
wait "$java_pid"

Additionally, you can use the preStop pod lifecycle hook to initiate a graceful shutdown of your application, for example:

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  containers:
    - name: my-container
      image: example.com/myimage
      lifecycle:
        preStop:
          httpGet:
            path: /shutdown
            port: 8080

This is useful when you cannot handle the SIGTERM signal in your application. See the references section for more information about lifecycle hooks.

If a process does not exit after receiving the SIGTERM signal, then OpenShift waits until the termination period expires, and sends a SIGKILL signal to the process. This signal immediately ends the process.

Note

Most runtimes and frameworks include built-in mechanisms to handle interruption signals by default. You should, however, confirm the behavior of your application during graceful shutdown before deploying it to OpenShift.

Reduce Image Size

Reduce the size of your custom containers by using the following techniques:

  • Avoid having too many RUN instructions in a Containerfile. Whenever possible, combine multiple commands into a single RUN instruction.

  • Exclude files and directories from the build context. Create a .containerignore or .dockerignore file in the build context directory to prevent unnecessary files and directories from being copied into the image.

  • Use multistage Containerfiles. With this approach, you can use the first stage of the Containerfile to build the application, and then copy the artifacts to the final stage, which includes only the necessary runtime dependencies. The resulting image is often smaller than regular single-stage images, which use the same stage for building and running the application.

Note

Do not confuse multistage builds, which is a technique at the Containerfile level, with chained builds. Chained builds is an OpenShift feature that chains multiple builds inside the cluster. OpenShift builds are covered later in the course.

Use a Minimal Image

Whenever possible, use a minimal UBI image to slim down the size of your containers. Note that the name of minimal images might differ, based on the runtime. For example, Node.js UBI images use names such as nodejs-18-minimal, but the OpenJDK UBI images call this concept runtime-only images, as in ubi9/opendjk-17-runtime.

If you use a multistage build, then use a standard UBI image as the initial, builder stage, such as ubi9/nodejs-18. Then, use a minimal image for the last stage, such as ubi9/nodejs-18-minimal to reduce the size of the resulting image.

Define Metadata Labels

Use the LABEL instruction to define information, warnings and suggestions that OpenShift can display to the users.

OpenShift supports a number of metadata labels. The label names are namespaced with the io.openshift or io.k8s prefixes. The OpenShift tooling parses these labels and provides them as additional information in the OpenShift UIs. For example, you can define the io.openshift.min-cpu label to make the OpenShift UI warn the users about the minimum number of CPUs that an image requires:

LABEL io.openshift.min-cpu 2

For a full list of all the image labels supported in OpenShift, refer to the references at the end of this section.

Use Working directories

Red Hat recommends using absolute paths in WORKDIR instructions. Use WORKDIR instead of multiple RUN instructions where you change directories and then run some commands. This approach ensures better maintainability and is easier to troubleshoot.

Set Environment variables

Use ENV instructions to define file and directory paths instead of reusing fixed paths in Containerfile instructions. Environment variables are useful to define application configuration, store information such as software version numbers, and also to append directories to the PATH environment variable.

Using the ARG instruction to set environment variables at build time is also a supported way to create reusable container images that run on OpenShift.

Declare Volumes

Red Hat recommends the explicit definition of volumes in Containerfiles, by using the VOLUME instruction. Defining the VOLUME instruction makes it easy for image consumers to understand what volumes they can define when running your image.

OpenShift does not use the VOLUME instruction. When you deploy container images, the cluster does not attach any volumes to the application by default.

Expose Ports

Expose non-privileged ports with the EXPOSE instruction. Defining the EXPOSE instruction makes it easy for image consumers to understand what ports your application exposes. Do not expose ports below 1024, which require privileged access.

On Red Hat OpenShift, the oc new-app command uses the EXPOSE value of the image. For example, if the EXPOSE value is 3001, then oc new-app sets the 3001 as the port used in the definition of the Deployment and Service resources. This mechanism also supports multiple ports in a single EXPOSE instruction.

In contrast, adding a new application from the OpenShift web console typically does not use the EXPOSE value. You must set the port explicitly by using web forms.

Build and Push Images with Podman

You can use a tool such as Podman to build a container image locally and push the image to a container registry.

Note

You can also use OpenShift to build your container images. The OpenShift build capabilities, such as Source-to-Image (S2I) and Docker builds, are covered later in this course.

Use the podman build command to create a container image from a Containerfile, as the following example shows:

[user@host ~]$ podman build CONTEXT_DIR -t IMAGE

The preceding command creates a local container image by using the Containerfile or Dockerfile at the CONTEXT_DIR directory. The produced image is called IMAGE. After building the container image locally, push the image to a container registry by using the podman push command.

[user@host ~]$ podman push IMAGE

Note

You must be logged in to the registry to push images. You can log in with the podman login command.

After the image is published in the container registry, you can deploy the image by using any of the methods that Red Hat OpenShift provides, such as the web console, or the oc and odo CLIs.

References

For more information about container best practices, refer to the Learning Container Best Practices section in the Creating Images chapter in the Red Hat OpenShift Container Platform 4.12 Images documentation at https://access.redhat.com/documentation/en-us/openshift_container_platform/4.12/html-single/images/index#images-create-guidelines_create-images

For more information about UBI images, refer to the Types of Container Images chapter in the Red Hat Enterprise Linux 9 Building, Running, and Managing Containers documentation at https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html-single/building_running_and_managing_containers/index#assembly_types-of-container-images_building-running-and-managing-containers

For more information about the benefits of UBI images, refer to the Characteristics of UBI Images section in the Types of Container Images chapter in the Red Hat Enterprise Linux 9 Building, Running, and Managing Containers documentation at https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html-single/building_running_and_managing_containers/index#con_characteristics-of-ubi-images_assembly_types-of-container-images

For more information about pulling UBI images, refer to the Container Registries section in the Working with Container Registries chapter in the Red Hat Enterprise Linux 9 Building, Running, and Managing Containers documentation at https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html-single/building_running_and_managing_containers/index#con_container-registries_working-with-container-registries

For more information about supported image metadata, refer to the Defining Image Metadata section in the Creating Images chapter in the Red Hat OpenShift Container Platform 4.12 Images documentation at https://access.redhat.com/documentation/en-us/openshift_container_platform/4.12/html-single/images/index#defining-image-metadata

For more information about chained builds, refer to the Chained Builds section in the Builds chapter in the Red Hat OpenShift Container Platform 4.12 CI/CD documentation at https://access.redhat.com/documentation/en-us/openshift_container_platform/4.12/html-single/cicd/index#builds-chaining-builds_advanced-build-operations

Revision: do288-4.12-0d49506