Creating Zstream Builder Images
This document provides explicit instructions for creating new RHEL zstream builder container images (RHEL 9+). It is designed to be followed by both humans and AI systems with minimal interpretation required.
Automation Note
This document has four phases, each ending with a merge request that requires human review. When following as an AI agent:
- Execute steps within a phase without asking for confirmation
- The build step (Phase 2, Step 3) takes several minutes - this is expected
- STOP at the end of each phase when you see
STOP: Human action required - Report the MR URL and wait for the user to confirm the merge before continuing
- Only proceed to the next phase after the user confirms the previous MR is merged
Overview
When a new RHEL zstream is released (e.g., 9.7, 10.1), a corresponding builder container image must be created. This requires coordinated changes across three repositories:
- cki-lib (gitlab.com/cki-project/cki-lib) - CI templates that reference the builder images
- containers (gitlab.com/cki-project/containers) - Container image definitions
- kernel (gitlab.com/redhat/rhel/src/kernel) - Kernel repository that uses the builder image
The process has four phases:
- Phase 1: Prepare cki-lib - Add the new image reference with
ONLY_DOWNSTREAMflag, merge immediately (safe because the flag prevents usage until the image exists) - Phase 2: Create Container Image - Build and publish the container image
- Phase 3: Enable in cki-lib - Remove the
ONLY_DOWNSTREAMflag to enable the image in main CI pipelines - Phase 4: Update Kernel Repository - Update the kernel repository to use the new builder image
Input Parameters
The process requires the following parameters:
{major}: RHEL major version (e.g.,9,10,11){minor}: RHEL minor version (e.g.,7,1,0){issue_url}: URL to the GitLab issue or Jira ticket tracking this work{containers_fork_remote}: Git remote name for your containers fork{cki_lib_repo_path}: Path to the cki-lib repository (e.g.,../cki-lib){cki_lib_fork_remote}: Git remote name for your cki-lib fork{kernel_repo_path}: Path to the kernel repository (e.g.,../kernel-rhel){kernel_upstream_remote}: Git remote for upstream kernel repo (e.g.,rhel-9){kernel_fork_remote}: Git remote for your kernel repo fork (e.g.,rhel-9-fork)
Note on kernel repo remotes: The kernel repository typically uses separate remotes for each RHEL major version:
- Upstream:
rhel-{major}→git@gitlab.com:redhat/rhel/src/kernel/rhel-{major} - Fork: e.g.,
rhel-{major}-fork→ user’s fork of the upstream repo
Zstream branches on each remote are named {major}.{minor} (e.g., 9.6, 9.7, 10.1).
Standard MR Workflow
All phases in this guide create merge requests by pushing to a personal fork. This requires having a fork of each repository with a git remote configured.
The workflow pattern is:
-
Navigate to the target repository (if not already there)
-
Update to latest main:
git checkout main && git pull -
Create a feature branch:
git checkout -b {branch_name} -
Make changes and stage files:
git add {files} -
Commit with issue reference:
git commit -m "{message}\n\n{issue_url}" -
Push to your fork and create MR targeting upstream. Where
{fork_remote}is the git remote pointing to your personal fork:git push \ -o merge_request.create \ -o merge_request.target=main \ -o merge_request.title="{title}" \ -o merge_request.description="Related issue: {issue_url}" \ {fork_remote} {branch_name} -
Return to original directory (if navigated away)
Each phase below provides the concrete commands with all values filled in.
Prerequisites
Before starting, verify the following:
1. Issue URL
You must have a GitLab issue or Jira ticket URL to associate with this work.
Verification: Confirm {issue_url} has been provided.
- If provided: Proceed to next prerequisite.
- If NOT provided: STOP and ask the user for the issue URL before continuing.
2. Network Access
You must have access to Red Hat internal network (VPN) to reach download.devel.redhat.com.
Verification command:
curl -s -o /dev/null -w "%{http_code}" https://download.devel.redhat.com/
Success criteria: Returns 200 or 301.
3. UBI Base Image Availability
The UBI base image for the target version must exist.
Verification command:
podman pull registry.access.redhat.com/ubi{major}/ubi:{major}.{minor}
Success criteria: Image pulls successfully.
4. Repository File Exists
Check if files/rhel{major}.z.repo exists in the containers repository.
Verification command:
test -f files/rhel{major}.z.repo && echo "EXISTS" || echo "MISSING"
- If
EXISTS: No action needed, proceed to fork setup. - If
MISSING: A new repo file must be created in Phase 2 (see 2.0 Create Repo File).
5. Containers Fork Setup
You need a fork of the containers repository with remotes configured.
Verification command:
git remote -v
Identify your fork remote: Look for a remote pointing to gitlab.com/{your-username}/containers.
Note this remote name as {containers_fork_remote}.
Example output:
fork git@gitlab.com:your-username/containers.git (fetch)
origin git@gitlab.com:cki-project/containers.git (fetch)
In this example, {containers_fork_remote} = fork.
6. cki-lib Fork Setup
You need access to the cki-lib repository with remotes configured.
Verification command:
ls {cki_lib_repo_path}/.gitlab/ci_templates/cki-templates.yml
Success criteria: File exists.
Identify your cki-lib fork remote:
cd {cki_lib_repo_path} && git remote -v && cd -
Note the remote name pointing to your cki-lib fork as {cki_lib_fork_remote}.
7. Kernel Repository Fork Setup
You need access to the kernel repository with remotes configured for the target RHEL major version.
Verification command:
cd {kernel_repo_path} && git remote -v && cd -
Identify your remotes: Look for:
- Upstream remote pointing to
gitlab.com/redhat/rhel/src/kernel/rhel-{major}(note this as{kernel_upstream_remote}) - Fork remote pointing to your fork (note this as
{kernel_fork_remote})
Example output:
rhel-9 git@gitlab.com:redhat/rhel/src/kernel/rhel-9 (fetch)
rhel-9-fork git@gitlab.com:your-username/rhel-9 (fetch)
In this example:
{kernel_upstream_remote}=rhel-9{kernel_fork_remote}=rhel-9-fork
8. Tools
Required: podman.
Verification command:
podman --version
Success criteria: Returns version information.
Decision Logic: AWSCLI Flag
Based on the major version, determine the AWSCLI installation flag:
| Major Version | Flag Value |
|---|---|
| 9 | SYSTEM |
| 10+ | MATCHING |
Decision rule:
IF major == 9 THEN flag = "SYSTEM"
ELSE IF major >= 10 THEN flag = "MATCHING"
Phase 1: Prepare cki-lib
This phase adds the new image reference to cki-lib with ONLY_DOWNSTREAM: 'true'.
This can be merged immediately because the flag prevents the image from being
used in CI until Phase 3.
1.1 Update cki-templates.yml
File: {cki_lib_repo_path}/.gitlab/ci_templates/cki-templates.yml
Location: Find the section with IMAGE_ARCHES: amd64 arm64 ppc64le s390x entries
near other builder images.
Action: Add a new block for the new image with ONLY_DOWNSTREAM: 'true':
- IMAGE_NAME:
- builder-rhel{major}.{minor}
IMAGE_ARCHES: amd64 arm64 ppc64le s390x
ONLY_DOWNSTREAM: 'true'
Note: Multiple new images can be added in the same block if onboarding several at once (e.g., both 9.7 and 10.1).
1.2 Create and Merge cki-lib MR
cd {cki_lib_repo_path}
git checkout -b onboard-rhel{major}.{minor}
git add .gitlab/ci_templates/cki-templates.yml
git commit -m "gitlab-ci: add rhel{major}.{minor} builder (downstream only)
{issue_url}"
git push \
-o merge_request.create \
-o merge_request.target=main \
-o merge_request.title="gitlab-ci: add rhel{major}.{minor} builder (downstream only)" \
-o merge_request.description="Related issue: {issue_url}" \
{cki_lib_fork_remote} onboard-rhel{major}.{minor}
cd -
Success criteria: MR is created.
STOP: Merge cki-lib preparation MR before continuing
The cki-lib MR must be reviewed and merged before proceeding. This is safe to
merge immediately because ONLY_DOWNSTREAM prevents the image from being used
until Phase 3.
Report the MR URL to the user and wait for confirmation that it has been merged.
Phase 2: Create Container Image
This phase creates the container image definition, builds it locally for validation, and submits an MR to the containers repository.
2.0 Create Repo File (new major versions only)
If this is the first zstream for a new major version and files/rhel{major}.z.repo
does not exist, create it based on the existing pattern.
Use files/rhel9.z.repo or files/rhel10.z.repo as a template:
- Copy the template:
cp files/rhel{existing}.z.repo files/rhel{major}.z.repo - Replace all version numbers with the new major version
- The
__Z__placeholder will be substituted with the minor version at build time
Note: This step is only needed for the first zstream of a new major version (e.g., 11.0). Skip to 2.1 if the repo file already exists.
2.1 Create the Container Image File
Create the file builds/builder-rhel{major}.{minor}.in with the following content:
#define _AWSCLI_PIP_{flag} 1
#define _RHEL_VERSION {major}
#define _RHEL_MINOR_VERSION {minor}
#include "setup-from-rhel"
#include "builder"
#include "cleanup"
Example: RHEL 9.7
File: builds/builder-rhel9.7.in
#define _AWSCLI_PIP_SYSTEM 1
#define _RHEL_VERSION 9
#define _RHEL_MINOR_VERSION 7
#include "setup-from-rhel"
#include "builder"
#include "cleanup"
Example: RHEL 10.1
File: builds/builder-rhel10.1.in
#define _AWSCLI_PIP_MATCHING 1
#define _RHEL_VERSION 10
#define _RHEL_MINOR_VERSION 1
#include "setup-from-rhel"
#include "builder"
#include "cleanup"
2.2 Update GitLab CI Configuration
Add the new image name to the CI build matrix in .gitlab-ci.yml.
Location: Find the .images section with parallel: matrix: containing the list of
IMAGE_NAME values.
Action: Add builder-rhel{major}.{minor} to the appropriate list based on visibility:
- For RHEL images: Add to the
internalvisibility section alongside otherbuilder-rhel*images
Example: To add builder-rhel10.1, find the section containing builder-rhel10.0 and add
the new image:
- IMAGE_NAME:
- builder-rhel8
- builder-rhel8.2
# ... other images ...
- builder-rhel10
- builder-rhel10.0
- builder-rhel10.1 # Add this line
AUTOMATIC_DEPLOYMENT: 'false'
VISIBILITY: internal
Verification command:
grep -q "builder-rhel{major}.{minor}" .gitlab-ci.yml && echo "FOUND" || echo "MISSING"
Success criteria: Returns FOUND.
2.3 Build the Container Image Locally
Run the build using the CKI buildah container:
podman run \
--rm \
--pull=newer \
--security-opt label=disable \
-e IMAGE_NAME=builder-rhel{major}.{minor} \
-w /code \
-v .:/code:z \
-v ~/.local/share/containers:/var/lib/containers:z \
quay.io/cki/buildah:production \
cki_build_image.sh
Note: The --security-opt label=disable flag is required to allow buildah
to access the shared container storage. This disables SELinux label enforcement
for the container, which is needed because the host’s container storage files
have SELinux contexts that would otherwise block access.
Success criteria: Command exits with code 0 and outputs build completion message.
Note: The local build creates the image with tag latest-amd64 (or the
architecture you’re building on).
2.4 Validate the Built Image
Run validation tests on the built image:
podman run --rm localhost/builder-rhel{major}.{minor}:latest-amd64 bash -c '
set -e
echo "Checking gcc..." && gcc --version
echo "Checking make..." && make --version
echo "Checking python3..." && python3 --version
echo "Checking build tools..." && which bison flex bc
echo "Running compile test..."
echo "int main(){return 0;}" > /tmp/test.c
gcc -o /tmp/test /tmp/test.c
/tmp/test
echo "SUCCESS: All validation tests passed"
'
Success criteria: Output ends with SUCCESS: All validation tests passed.
2.5 Create and Submit Merge Request
2.5.1 Create a Branch
git checkout -b add-builder-rhel{major}.{minor}
2.5.2 Stage and Commit
git add builds/builder-rhel{major}.{minor}.in .gitlab-ci.yml
git commit -m "Add builder-rhel{major}.{minor} container image
{issue_url}"
2.5.3 Push to Fork and Create MR
git push \
-o merge_request.create \
-o merge_request.target_project=cki-project/containers \
-o merge_request.target=main \
-o merge_request.title="Add builder-rhel{major}.{minor} container image" \
-o merge_request.description="Related issue: {issue_url}" \
{containers_fork_remote} add-builder-rhel{major}.{minor}
Success criteria: Command outputs MR URL.
STOP: Merge containers MR and verify image publication
The containers MR must be reviewed, merged, and the CI pipeline must complete to build and publish the container images.
Report the MR URL to the user and wait for confirmation that:
- The MR has been merged
- The container images have been published
Verification commands (replace {mr_id} with the MR number):
# Check MR pipeline completed (image tagged with MR number)
skopeo inspect docker://quay.io/cki/builder-rhel{major}.{minor}:mr-{mr_id}
# Check production deployment completed (may require manual deployment)
skopeo inspect docker://quay.io/cki/builder-rhel{major}.{minor}:production
Both images must be available before proceeding to Phase 3.
Phase 3: Enable in cki-lib
After the containers MR is merged and the images are published, update cki-lib
to move the image from the ONLY_DOWNSTREAM block to the main image list.
3.1 Update cki-templates.yml
File: {cki_lib_repo_path}/.gitlab/ci_templates/cki-templates.yml
Action: Remove the image from the ONLY_DOWNSTREAM block and add it to the
main image list (the block without ONLY_DOWNSTREAM that contains other
builder-rhel* images).
Before:
- IMAGE_NAME:
- builder-rhel9.6
# other images...
IMAGE_ARCHES: amd64 arm64 ppc64le s390x
- IMAGE_NAME:
- builder-rhel{major}.{minor}
IMAGE_ARCHES: amd64 arm64 ppc64le s390x
ONLY_DOWNSTREAM: 'true'
After:
- IMAGE_NAME:
- builder-rhel9.6
- builder-rhel{major}.{minor} # Moved here
# other images...
IMAGE_ARCHES: amd64 arm64 ppc64le s390x
# ONLY_DOWNSTREAM block removed (or emptied if other images remain)
3.2 Create and Merge cki-lib MR
cd {cki_lib_repo_path}
git checkout main
git pull
git checkout -b enable-rhel{major}.{minor}
git add .gitlab/ci_templates/cki-templates.yml
git commit -m "gitlab-ci: enable rhel{major}.{minor} builder
{issue_url}"
git push \
-o merge_request.create \
-o merge_request.target=main \
-o merge_request.title="gitlab-ci: enable rhel{major}.{minor} builder" \
-o merge_request.description="Related issue: {issue_url}" \
{cki_lib_fork_remote} enable-rhel{major}.{minor}
cd -
Success criteria: Command outputs MR URL.
STOP: Merge cki-lib MR before continuing
The cki-lib MR must be reviewed and merged before proceeding to Phase 4.
Report the MR URL to the user and wait for confirmation that it has been merged.
Phase 4: Update Kernel Repository
After the container image is published and enabled in cki-lib, update the kernel
repository to use the new builder image. New zstream branches use the ystream
builder image (e.g., builder-rhel9) as a fallback until the zstream container
is available. This phase updates builder_image to the newly created zstream image.
4.1 Checkout the Zstream Branch
cd {kernel_repo_path}
git fetch {kernel_upstream_remote}
git checkout -b use-builder-{major}.{minor} {kernel_upstream_remote}/{major}.{minor}
4.2 Update builder_image in .gitlab-ci.yml
File: .gitlab-ci.yml
Location: Find the trigger pipeline section for the RHEL major version.
Action: Change builder_image from the ystream fallback to the zstream image:
Before:
.trigger_rhel{major}_pipeline:
trigger:
branch: rhel{major}
variables:
builder_image: quay.io/cki/builder-rhel{major}
After:
.trigger_rhel{major}_pipeline:
trigger:
branch: rhel{major}
variables:
builder_image: quay.io/cki/builder-rhel{major}.{minor}
4.3 Create and Submit Merge Request
The kernel repository requires a specific commit message format:
git add .gitlab-ci.yml
git commit -s -m "gitlab-ci: use rhel{major}.{minor} builder image
ubi{major}.{minor} has been released.
JIRA: INTERNAL
Upstream Status: RHEL only
Part of {issue_url}"
git push \
-o merge_request.create \
-o merge_request.draft \
-o merge_request.target={major}.{minor} \
-o merge_request.title="gitlab-ci: use rhel{major}.{minor} builder image" \
-o "merge_request.description=ubi{major}.{minor} has been released.\n\nJIRA: INTERNAL\n\nUpstream Status: RHEL only\n\nPart of {issue_url}\n\nSigned-off-by: $(git config user.name) <$(git config user.email)>" \
{kernel_fork_remote} use-builder-{major}.{minor}
cd -
Note: The -s flag adds the Signed-off-by line automatically. Use literal \n
for newlines in the description since push options cannot contain raw newlines.
Success criteria: Command outputs MR URL.
STOP: Wait for pipeline and mark MR ready
- Wait for the MR pipeline to complete successfully
- Mark the MR as ready (remove draft status)
- The kernel stream maintainers will review and merge the MR on their own schedule
Report the MR URL to the user. The onboarding process is complete once the MR is submitted. The kernel maintainers will merge it when ready, after which the new builder image will be in use by the kernel CI pipelines.
Complete Example: Creating builder-rhel9.7
Assuming:
{issue_url}=https://gitlab.com/cki-project/cki-lib/-/issues/123{containers_fork_remote}=fork{cki_lib_repo_path}=../cki-lib{cki_lib_fork_remote}=fork{kernel_repo_path}=../kernel-rhel{kernel_upstream_remote}=rhel-9{kernel_fork_remote}=rhel-9-fork
Example: Phase 1
cd ../cki-lib
git checkout main && git pull
# Edit .gitlab/ci_templates/cki-templates.yml
# Add the following block:
#
# - IMAGE_NAME:
# - builder-rhel9.7
# IMAGE_ARCHES: amd64 arm64 ppc64le s390x
# ONLY_DOWNSTREAM: 'true'
git checkout -b onboard-rhel9.7
git add .gitlab/ci_templates/cki-templates.yml
git commit -m "gitlab-ci: add rhel9.7 builder (downstream only)
https://gitlab.com/cki-project/cki-lib/-/issues/123"
git push \
-o merge_request.create \
-o merge_request.target=main \
-o merge_request.title="gitlab-ci: add rhel9.7 builder (downstream only)" \
-o merge_request.description="Related issue: https://gitlab.com/cki-project/cki-lib/-/issues/123" \
fork onboard-rhel9.7
cd -
# STOP: Get MR reviewed and merged before continuing
Example: Phase 2
# Prerequisites check
curl -s -o /dev/null -w "%{http_code}" https://download.devel.redhat.com/
podman pull registry.access.redhat.com/ubi9/ubi:9.7
test -f files/rhel9.z.repo && echo "Repo file exists"
# Create the .in file
cat > builds/builder-rhel9.7.in << 'EOF'
#define _AWSCLI_PIP_SYSTEM 1
#define _RHEL_VERSION 9
#define _RHEL_MINOR_VERSION 7
#include "setup-from-rhel"
#include "builder"
#include "cleanup"
EOF
# Update .gitlab-ci.yml - add builder-rhel9.7 to the internal images list
# Find the section with builder-rhel9.6 and add builder-rhel9.7 after it
sed -i '/- builder-rhel9.6$/a\ - builder-rhel9.7' .gitlab-ci.yml
# Verify the change
grep -q "builder-rhel9.7" .gitlab-ci.yml && echo "CI file updated" || echo "CI update failed"
# Build
podman run \
--rm \
--pull=newer \
--security-opt label=disable \
-e IMAGE_NAME=builder-rhel9.7 \
-w /code \
-v .:/code:z \
-v ~/.local/share/containers:/var/lib/containers:z \
quay.io/cki/buildah:production \
cki_build_image.sh
# Validate
podman run --rm localhost/builder-rhel9.7:latest-amd64 bash -c '
set -e
gcc --version && make --version && python3 --version
which bison flex bc
echo "int main(){return 0;}" > /tmp/t.c && gcc -o /tmp/t /tmp/t.c && /tmp/t
echo "SUCCESS: All validation tests passed"
'
# Create MR
git checkout -b add-builder-rhel9.7
git add builds/builder-rhel9.7.in .gitlab-ci.yml
git commit -m "Add builder-rhel9.7 container image
https://gitlab.com/cki-project/cki-lib/-/issues/123"
git push \
-o merge_request.create \
-o merge_request.target_project=cki-project/containers \
-o merge_request.target=main \
-o merge_request.title="Add builder-rhel9.7 container image" \
-o merge_request.description="Related issue: https://gitlab.com/cki-project/cki-lib/-/issues/123" \
fork add-builder-rhel9.7
# STOP: Get MR reviewed and merged, wait for CI to publish images
# Verify images are available (replace 123 with actual MR number):
# skopeo inspect docker://quay.io/cki/builder-rhel9.7:mr-123
# skopeo inspect docker://quay.io/cki/builder-rhel9.7:production
Example: Phase 3
cd ../cki-lib
git checkout main && git pull
# Edit .gitlab/ci_templates/cki-templates.yml
# Move builder-rhel9.7 from ONLY_DOWNSTREAM block to main image list
git checkout -b enable-rhel9.7
git add .gitlab/ci_templates/cki-templates.yml
git commit -m "gitlab-ci: enable rhel9.7 builder
https://gitlab.com/cki-project/cki-lib/-/issues/123"
git push \
-o merge_request.create \
-o merge_request.target=main \
-o merge_request.title="gitlab-ci: enable rhel9.7 builder" \
-o merge_request.description="Related issue: https://gitlab.com/cki-project/cki-lib/-/issues/123" \
fork enable-rhel9.7
cd -
# STOP: Get MR reviewed and merged before continuing
Example: Phase 4
cd ../kernel-rhel
git fetch rhel-9
git checkout -b use-builder-9.7 rhel-9/9.7
# Edit .gitlab-ci.yml
# Find .trigger_rhel9_pipeline and change:
# builder_image: quay.io/cki/builder-rhel9
# to:
# builder_image: quay.io/cki/builder-rhel9.7
git add .gitlab-ci.yml
git commit -s -m "gitlab-ci: use rhel9.7 builder image
ubi9.7 has been released.
JIRA: INTERNAL
Upstream Status: RHEL only
Part of https://gitlab.com/cki-project/cki-lib/-/issues/123"
git push \
-o merge_request.create \
-o merge_request.draft \
-o merge_request.target=9.7 \
-o merge_request.title="gitlab-ci: use rhel9.7 builder image" \
-o "merge_request.description=ubi9.7 has been released.\n\nJIRA: INTERNAL\n\nUpstream Status: RHEL only\n\nPart of https://gitlab.com/cki-project/cki-lib/-/issues/123\n\nSigned-off-by: $(git config user.name) <$(git config user.email)>" \
rhel-9-fork use-builder-9.7
cd -
# STOP: Wait for pipeline to complete, then mark MR ready (remove draft)
# Kernel maintainers will merge on their own schedule - onboarding complete!
Troubleshooting
Build fails with network errors
Ensure you are connected to Red Hat VPN and can access download.devel.redhat.com.
UBI image not found
The UBI image for the target version may not be published yet. Wait for Red Hat to publish the UBI image before creating the builder.