Background documentation sourced from the containers repository.
This is the multi-page printable view of this section. Click here to print.
Containers repository
- 1: Image Pipeline
- 2: Global Variables Reference
- 3: Konflux Resource Deployment
- 4: Security Labels and Metadata
- 5: Container Image Labels
- 6: CI Scripts
- 6.1: build_images.sh
- 6.2: run_tests_container.sh
- 6.3: run_tests_k8s.sh
- 6.4: retrigger_failed_checks.py
- 6.5: gitlab_sync.py
1 - Image Pipeline
Explanation of the complete container image pipeline, from RPM dependency updates through source templates, generation, build, testing, release, and advisory publication.
Overview
The container image pipeline consists of six main stages:
- Source Templates - Jinja2 templates in
images/*/define container images - Generation - Templates are rendered into Containerfiles and Konflux resources
- Build - Konflux builds multi-architecture container images
- Testing - Testing Farm runs integration tests via Konflux
- Enterprise Contract Validation - Conforma validates policy compliance before release
- Release - Images are published to registries and advisories are created
RPM packages are built and published separately via the RPM Pipeline. This document covers what happens after RPMs are available in the Hummingbird package repository.
RPM Dependency Updates
Container images pin exact RPM versions via lockfiles. When new RPMs are published to the Hummingbird Pulp repository, lockfiles must be regenerated for images to pick up the updates.
Lockfile structure
Each image variant has two RPM-related files:
| File | Purpose |
|---|---|
images/<image>/<distro>/<variant>/rpms/rpms.in.yaml |
Declares required packages |
images/<image>/<distro>/<variant>/rpms/rpms.lock.yaml |
Pins exact versions, URLs, and checksums |
The lockfile contains every RPM (direct and transitive dependencies) with its exact version, architecture, URL pointing to the Pulp repository, and checksum. During build, the locked RPMs are downloaded in hermetic mode — no network access to external repositories.
Automatic lockfile updates
MintMaker / Renovate — A custom Renovate instance (forked
rpm-lockfile manager from redhat-exd-rebuilds/renovate) runs as a
Kubernetes CronJob. It scans each rpms.in.yaml file individually,
resolves dependencies against Pulp repositories, and opens auto-merge
MRs — one per image group and distro (e.g., “Refresh RPM lockfiles for
go-1-25/hummingbird”).
The underlying tool is rpm-lockfile-prototype, which wraps the DNF
dependency solver. For a given rpms.in.yaml, it resolves all direct
and transitive dependencies against the yum repositories defined in
yum-repos/*.repo and outputs a complete rpms.lock.yaml with pinned
versions, download URLs, and checksums. Locally, generate_rpms_lock.py
adds hash-based optimization: it hashes the input file and skips
regeneration when the hash matches the existing lockfile, avoiding
expensive solver runs when nothing has changed.
Manual lockfile updates
To refresh the lockfile for a single image variant:
make images/<image>/<distro>/<variant>/rpms/rpms.lock.yaml FORCE_REFRESH=true
For example:
make images/caddy/hummingbird/default/rpms/rpms.lock.yaml FORCE_REFRESH=true
To regenerate all lockfiles and open MRs for the changes:
# Regenerate all rpms.lock.yaml files
make all-host FORCE_REFRESH=true
# Open MRs for any changed lockfiles
ci/create_lockfile_update_mrs.sh
From lockfile MR to container build
When a lockfile MR merges to main, PipelinesAsCode triggers a container build (Stage 3). Each image variant has CEL expressions in its PipelineRun definition that match on file paths, so only images whose lockfiles changed get rebuilt.
Stage 1: Source Templates
Each container image is defined by templates in images/<image-name>/:
properties.yml- Image configuration (packages, variants, tags, etc.)Containerfile.j2- Jinja2 template for the container buildREADME.md.j2- Documentation templatetests-container.yml- Integration test definitions
Templates use reusable macros from macros/*.yml.j2:
setup_newroot()- Configures DNF and filesysteminstall_newroot()- Installs packagescleanup_newroot()- Cleans up filesfinal_stage()- Creates scratch-based final image
Shared configuration is defined in images/variables.yml. See Global Variables
Reference for details.
Stage 2: Generation
Templates are rendered into concrete artifacts that drive the pipeline:
- Containerfiles - Build instructions for each image variant
- README documentation - Image documentation for Quay.io
- Konflux resources - CI/CD pipeline definitions
Containerfile Generation
Containerfiles are generated from templates for each image variant using Make’s incremental build system:
make
This combines:
- Reusable macros from
macros/ - Service-specific templates from
images/*/Containerfile.j2 - Configuration from
properties.yml(see Image Configuration Reference) - Variables from
images/variables.yml(see Global Variables Reference) - RPM versions from
rpms.lock.yamlfiles - Git submodule information from
.gitmodules
Output: images/<image-name>/<variant>/Containerfile, along with VERSION and TAGS files
The build system uses timestamp-based dependency tracking, so only changed files are regenerated.
README Generation
After Containerfiles are generated, README documentation is generated from README.md.j2 templates:
- Tag values are extracted from the generated Containerfile labels
- README is rendered using macros from
macros/readme.yml.j2 - Generated README includes actual version tags from the Containerfile
Output: images/<image-name>/README.md
Konflux Resource Generation
Konflux CI/CD resources are generated from templates in konflux-templates/:
make
This generates:
- Components - Define what to build (one per image variant)
- ImageRepositories - Define where to push images
- ReleasePlanAdmissions - Define how to release images
Output: konflux-templates/rendered.yml
These resources must be deployed to Konflux before builds can run. See the Konflux Resource Deployment guide for how and when resources are deployed.
Stage 3: Build
Konflux builds container images automatically when changes are pushed to GitLab.
Build Triggers
- Merge Requests: Builds all changed images and triggers tests
- Main Branch: Builds all changed images (tests do not run on main)
Build Process
For each image variant:
- Konflux Component watches the GitLab repository
- On changes, Konflux triggers a build PipelineRun
- The build uses the generated Containerfile from
images/<name>/<variant>/Containerfile - Images are built for multiple architectures (x86_64 and aarch64)
- Built images are pushed to the development registry
Build Output
Development images are pushed to the Red Hat User Workloads registry:
quay.io/redhat-user-workloads/hummingbird-tenant/<group>--<variant>--main
Merge request builds are tagged with:
quay.io/redhat-user-workloads/hummingbird-tenant/<group>--<variant>--main:on-mr-<MR_ID>-<COMMIT_SHA>
Examples:
quay.io/redhat-user-workloads/hummingbird-tenant/curl--default--mainquay.io/redhat-user-workloads/hummingbird-tenant/nginx--builder--main:on-mr-123-abc1234
SBOM Generation
Each per-architecture build also produces an SPDX 2.3 Software Bill of Materials (SBOM), attached as an OCI artifact to the image. The SBOM is assembled from two independent scans, merged into a single document:
flowchart LR
syft["Syft\n(buildah-remote-oci-ta)"] --> mobster["Mobster\n(buildah-remote-oci-ta)"]
hermeto["Hermeto\n(prefetch-dependencies-oci-ta)"] --> mobster
mobster --> sbom["Per-arch SPDX SBOM"]
sbom --> index["Mobster\n(build-image-index)"]
index --> oci["Index SBOM\n(.sbom OCI artifact)"]
- Syft runs as the
sbom-syft-generatestep inside the buildah-remote-oci-ta Tekton task. It scans the RPM database of the built per-arch image and finds installed binary RPMs plus non-RPM packages (Go modules, pip packages, etc.). One SBOM is produced per architecture. - Hermeto runs inside the prefetch-dependencies-oci-ta Tekton task. It records all build-time dependencies from lockfiles. A single Hermeto SBOM covers all architectures and source RPMs.
- Mobster runs as the
prepare-sbomsstep inside buildah-remote-oci-ta, merging the Syft and Hermeto SBOMs into one SPDX document per architecture. A second Mobster invocation in the build-image-index task generates an index-level SBOM and attaches it as an OCI artifact.
See Security Labels and Metadata for the SBOM entry structure and how to access SBOMs from the registry.
Stage 4: Testing
Images are validated through two types of integration tests:
- Container tests (
tests-container.yml) - Run with Podman and Docker via Testing Farm on RHEL-9-Nightly systems - K8s tests (
tests-k8s.yml) - Run in Konflux ephemeral Kubernetes namespaces
Test results appear as external jobs in GitLab CI pipelines, providing pass/fail status and links to the Konflux PipelineRun.
Test Triggering
Tests are only triggered on merge requests, not on main branch builds.
For an image group at images/<group-name>/, tests are triggered for all changes below that
directory, excluding documentation-only changes.
Any changes below .tekton/ or ci/ will trigger tests for the caddy image to ensure
infrastructure changes work before merging.
Test Execution
For each image group, pipelines are created per variant. If there is more than one variant in an image group, additional group pipelines are created. See Konflux group snapshot documentation for details.
Tests are selected based on the variants field in test files. Additionally, global tests from
ci/{variant}-tests/ are included. Group pipelines run tests that specify variants: [group],
allowing validation across multiple variants.
Reverse Dependency Testing
When a base image like core-runtime changes, dependent images (rust, xcaddy, etc.) need to be
tested to ensure compatibility. Reverse dependency testing is enabled by default. Images can opt-out
by setting reverse_dependency_tests: false in their properties.yml. This is recommended for
images like “curl” which are widely used and only have a small API which their own tests cover well
enough.
For container tests, dependent images are rebuilt locally in the testing environment to prevent version skew and ensure dependent images are in sync with the repository status in the merge request under test.
Integration Test Scenarios
Integration tests are triggered via IntegrationTestScenario resources defined in the
infrastructure repository:
| Application | Purpose |
|---|---|
containers-hummingbird |
Red Hat supported Hummingbird images |
containers-community-hummingbird |
Community images (support_level: community) |
containers-ci-hummingbird |
Experimental/infrastructure images (support_level: experimental) |
containers-rawhide |
All Rawhide-based images |
Container Testing
Container tests run via Testing Farm on RHEL-9-Nightly systems for both x86_64
and aarch64 architectures.
Container Testing Flow
- Developer opens MR modifying
images/nginx/ - GitLab CI pipeline and Konflux pipelines start in parallel
- Konflux builds
nginximage variants IntegrationTestScenariotriggers Testing Farm job- tmt discovers fmf plan (
ci/run_tests_container.fmf) - Testing Farm provisions machines (
x86_64andaarch64with RHEL-9-Nightly) - tmt sets up the testing environment
- tmt runs tests via
ci/run_tests_container.sh
Container Test ITS Configuration
The container tests use the upstream Testing Farm pipeline for Konflux CI:
kind: IntegrationTestScenario
spec:
contexts:
- {name: pull_request}
params:
- {name: COMPOSE, value: RHEL-9-Nightly}
- {name: ARCH, value: x86_64|aarch64} # one for each
- {name: IMAGE_TAG, value: v3.2}
resolverRef:
resolver: bundles
params:
- {name: bundle, value: quay.io/testing-farm/tmt-via-testing-farm:$(params.IMAGE_TAG)}
- {name: name, value: tmt-via-testing-farm}
- {name: kind, value: pipeline}
FMF Test Plan
The root folder of the containers repository is marked with a .fmf directory to enable Testing
Farm support. The actual test plan in ci/run_tests_container.fmf runs the following steps:
- Install
podmanfor container testing - Install Docker and start the Docker daemon on the host for Docker integration tests
- Fix git submodules until TFT-3991 is resolved
- For regular pipelines:
- Verify the image built by Konflux is reproducible using
ci/test_rebuild.sh - If needed, build reverse dependency images locally via Buildah
- Run Podman and Docker tests via
ci/run_tests_container.sh --component-name
- Verify the image built by Konflux is reproducible using
- For group pipelines:
- Run Podman and Docker group tests via
ci/run_tests_container.sh --group-component-name
- Run Podman and Docker group tests via
Container Test Environment
Testing Farm provides these environment variables to the test plan:
| Variable | Description |
|---|---|
IMAGE_NAME |
Single component (e.g., curl--default--main) |
IMAGE_NAMES |
Multiple components for group pipelines |
IMAGE_URL |
Image URL from Konflux |
IMAGE_URL_... |
Image URLs from Konflux for group pipelines |
SNAPSHOT_b64 |
Snapshot metadata (base64 encoded) |
K8s Testing
K8s tests run in Konflux ephemeral namespaces provisioned via EaaS (Environment as a Service).
K8s Testing Flow
- Developer opens MR modifying an image in the containers repository (e.g.,
nginx) - GitLab CI pipeline and Konflux pipelines start in parallel
- Konflux builds image variants
IntegrationTestScenariotriggers K8s test pipeline- Pipeline checks for
tests-k8s.yml; skips with SUCCESS if not found - Pipeline provisions ephemeral namespace via Konflux EaaS (tied to PipelineRun lifecycle)
- Pipeline fetches source via Trusted Artifacts
- Tests run via
ci/run_tests_k8s.shwith kubeconfig for ephemeral namespace - Pipeline fails if any test reports non-SUCCESS, ensuring GitLab sees correct status
K8s Test ITS Configuration
The K8s tests use the k8s-test-pipeline:
kind: IntegrationTestScenario
spec:
contexts:
- {name: pull_request}
resolverRef:
resolver: bundles
params:
- {name: bundle, value: quay.io/hummingbird-ci/k8s-test-pipeline:latest}
- {name: name, value: k8s-test}
- {name: kind, value: pipeline}
Stage 5: Enterprise Contract Validation
Before images can be released, they must pass Enterprise Contract (also known as Conforma) policy validation. This ensures images meet security, compliance, and build quality standards. These checks can also be run locally against Konflux-built images.
Policy Validation
Enterprise Contract validates that:
- Images are built using trusted, verified Tekton tasks
- Builds are hermetic (network-isolated with pre-fetched dependencies)
- Required security tests have passed
- Images have proper metadata and labels
- Build artifacts meet supply chain security requirements
Policy Configuration
Policies are defined as EnterpriseContractPolicy resources in
konflux-templates/macros/policy.yml.j2:
containers-hummingbird/containers-rawhide- Strict policies for production imagescontainers-community-hummingbird- Policy for community-supported images in the containers repository (same base exclusions as production policies)
These policies use the @redhat rule collection from the ec-release-policy.
Policy Exclusions
The following checks are excluded from the default @redhat policy set. When modifying exclusions,
update the policy macro and this documentation.
Test Package
The test package verifies that each build was subjected to a set of tests and that those tests all passed.
Snyk SAST checks (test.required_tests_passed:sast-snyk-check, test.no_skipped_tests:sast-snyk-check,
test.required_tests_passed:sast-snyk-check-oci-ta, test.no_skipped_tests:sast-snyk-check-oci-ta)
are excluded because Hummingbird images are currently not supported by Snyk.
Red Hat certification preflight checks (test.no_failed_tests:ecosystem-cert-preflight-checks,
test.no_erred_tests:ecosystem-cert-preflight-checks) are excluded because Hummingbird images are
not yet published to the Red Hat certified container registry.
Informative test failures (test.no_failed_informative_tests) are excluded because these produce
warnings for advisory purposes only and are explicitly non-blocking.
Deprecated image warnings (test.no_test_warnings:deprecated-image-check) are excluded because
final images are built FROM scratch, and the builder image is updated via Renovate like all other
images.
Trusted Task Package
The trusted_task package verifies that all Tekton Tasks involved in building the image are trusted by comparing Task references with a pre-defined list of trusted Tasks.
The trusted_task.current check warns when newer versions of tasks are available. This is excluded
because we use stable pinned task versions and control upgrade timing via Renovate rather than
requiring the latest version at all times.
RPM Repos Package
The rpm_repos package confirms that all RPM packages listed in SBOMs specify a known and permitted repository ID.
The rpm_repos.ids_known check is excluded because images use the internal
hummingbird repository and Fedora repositories, which are not
in the upstream known_rpm_repositories.yml list (that file only contains Red Hat
official repositories).
Labels Package
The labels package checks if the image has the expected labels set, including required and optional labels for Red Hat container certification.
Both labels.required_labels and labels.optional_labels are excluded because images currently
only include basic labels (maintainer, license_terms, name, cpe) and version labels, not
the full set of Red Hat certification labels (vendor, version, release, summary,
description, url, etc.) required for Red Hat Ecosystem Catalog publishing.
Buildah Build Task Package
The buildah_build_task package verifies buildah build task parameters.
The buildah_build_task.privileged_nested_param check verifies that PRIVILEGED_NESTED is not set
to true. This is excluded because images use the dnf-installroot helper from
the builder image to build all containers, including the builder image itself. This script requires
privileged operations (unshare, mount -t tmpfs, mount --bind for /proc and /dev/*) to set
up the install root environment (see commit 3668af16).
Schedule Package
The schedule package verifies that releases conform to a given schedule, including weekday restrictions.
The schedule.weekday_restriction check is excluded to allow releases any day including weekends.
The @redhat policy restricts weekend releases, but this project needs the ability to ship urgent
CVE fixes immediately regardless of the day of week.
CVE Package
The cve package checks for blocking and non-blocking CVEs in container images.
The cve.cve_blockers check is excluded because blocking on known CVEs would prevent releasing
images that fix other CVEs. A VEX feed could suppress false positives for RPM-level CVEs, but CVEs
can also originate from other artifact types where VEX does not apply (see MR !2227).
Hermetic Task Package (CI-Only)
The hermetic_task package verifies that tasks were invoked with the proper parameters to perform a hermetic (network-isolated) execution.
All Konflux applications in the containers repository enforce the hermetic task check. All
applications allow quay.io/hummingbird-ci/ as a base image source because images build
FROM quay.io/hummingbird-ci/hummingbird-builder via setup_newroot.yml.j2.
Stage 6: Release
After images pass testing in merge requests and are merged to main, they are released to public registries.
Registry Organization
Images are published to different registries based on distro and purpose:
| Registry | Purpose |
|---|---|
registry.access.redhat.com/hi/ |
Red Hat supported Hummingbird images (production) |
quay.io/hummingbird/ |
Red Hat supported Hummingbird images (mirror) |
quay.io/hummingbird-community/ |
Community-supported Hummingbird images |
quay.io/hummingbird-rawhide/ |
All Rawhide-based images |
quay.io/hummingbird-ci/ |
CI and tools images |
Production Registry: Red Hat supported Hummingbird images are published to
registry.access.redhat.com/hi/ via the rh-advisories release pipeline and
mirrored to quay.io/hummingbird/.
Community Registry (hummingbird-community): Publishes community-supported images
(those with support_level: community in properties.yml). Examples include minio,
minio-client, and bootc-os.
CI/tools Registry (hummingbird-ci): Publishes tooling and infrastructure images. This
includes tools-repo images (built outside the containers repository) and experimental images
from the containers repository (support_level: experimental), such as hummingbird-builder.
Release Process
- Merge request passes all tests
- Merge request is merged to main branch
- Konflux builds images from main
- ReleasePlanAdmission resources trigger the release pipeline
- Images are signed via Cosign for supply chain attestation
- Images are copied from the Konflux registry to target registries
- Tags are applied based on
properties.ymlconfiguration - For production releases: images are registered in the Red Hat container catalog via Pyxis
Release Mechanism
Releases are configured via ReleasePlanAdmission (RPA) resources in the containers repo and ReleasePlan resources in the infrastructure repo:
- ReleasePlanAdmission - Defines per-registry release configuration (target registry, tags, visibility settings)
- ReleasePlan - Triggers the release pipeline for a specific application and registry
Each distro/registry combination has its own RPA.
Production release pipeline
The production release uses the rh-advisories pipeline from the release-service-catalog.
The push-snapshot task pushes images from the development registry to:
- Production:
registry.access.redhat.com/hi/<image>:<tags>
Configuration is defined in releng/hummingbird-containers-prod.yaml.
Image signing
Images are signed using Cosign as part of the release pipeline. Signing provides supply chain attestation, allowing consumers to verify image provenance.
Pyxis catalog registration
The release pipeline registers images in the Red Hat container catalog via the Pyxis API:
- Product: Red Hat Hardened Images (Product ID 1071)
- Metadata: version, categories, layer information, SBOM references
- Configuration:
releng/pyxis-hummingbird.yaml
Pyxis registration makes images discoverable in the Red Hat Ecosystem Catalog and links them to security advisories.
Release Output
Released images are published to registries based on distro and support level:
registry.access.redhat.com/hi/<image-repository>:<image-tag>
quay.io/hummingbird/<image-repository>:<image-tag>
quay.io/hummingbird-community/<image-repository>:<image-tag>
quay.io/hummingbird-rawhide/<image-repository>:<image-tag>
quay.io/hummingbird-ci/<image-repository>:<image-tag>
Examples:
registry.access.redhat.com/hi/curl:8- Production Red Hat supported imagequay.io/hummingbird/nodejs:20- Red Hat supported Hummingbird imagequay.io/hummingbird-community/minio:latest- Community-supported imagequay.io/hummingbird-ci/hummingbird-builder:latest- Hummingbird Builder imagequay.io/hummingbird-rawhide/curl:latest- Rawhide image
Release Tags
Tags are extracted from Containerfile labels as defined in properties.yml:
latest- Latest version of the image<major>- Major version (e.g.,20for Node.js 20.x)<major>.<minor>- Major.minor version (e.g.,20.11)<full-version>- Complete version with release (e.g.,20.11.1-1.fc42)<timestamp>- Build timestamp (production releases only)
Non-default variants receive a -<variant> suffix (e.g., latest-builder).
Advisory Creation
When the production release pipeline runs, advisories are created:
- The Release Service generates an Advisory YAML from ReleasePlan and ReleasePlanAdmission metadata
- The advisory is pushed to the advisories repo on CEE GitLab
- GitLab CI validates the advisory against the schema and enforces field-level permissions
- On merge to main, the advisory is published via UMB and Kafka
Advisory types:
| Type | Meaning |
|---|---|
| RHSA | Security Advisory (contains CVE fixes) |
| RHBA | Bug Fix Advisory |
| RHEA | Enhancement Advisory |
After publication, the Pyxis API links each image record to its advisory via
image_advisory_id.
VEX Feed Update
After advisory publication, Red Hat SDEngine generates public vulnerability data:
- CSAF VEX documents (per-CVE) published at
https://security.access.redhat.com/data/csaf/v2/vex-feed/ - CSAF Advisory documents (per-advisory) published at
https://security.access.redhat.com/data/csaf/v2/advisories/
The hummingbird-vex-feed repository maintains CPE mappings that associate
Hummingbird package repositories with cpe:/a:redhat:hummingbird:1.
Image Documentation
When a commit to main changes a README.md file, the content is automatically
pushed to quay.io as the image description via the update_quay_description
job.
References
- RPM Pipeline - How RPM spec changes flow through the build pipeline to the package repository
- Development Workflow - Practical guide for contributing
- Adding Images - Step-by-step guide for adding new images
- Testing Guide - How to run and write tests locally
- Image Configuration Reference - Complete
properties.ymlreference
2 - Global Variables Reference
Complete reference for global configuration settings that apply to all container images.
Overview
The images/variables.yml file contains global configuration that applies to
all container images in the repository. These settings define project-wide
defaults, security parameters, and variant-specific behavior.
Location: images/variables.yml (in the containers repository root)
Scope: All images inherit these settings unless overridden in
image-specific properties.yml
Configuration Reference
cpe
- Type: String (CPE 2.2 formatted string)
- Default:
"cpe:/a:redhat:hummingbird:1" - Description: Common Platform Enumeration (CPE) identifier for all Hummingbird containers.
- Usage: Automatically added as a label to all generated container images
and passed to
inject-source-infoduring the build process. - Format: CPE 2.2 URI format:
cpe:/part:vendor:product:version
default_user
- Type: String (numeric UID)
- Default:
"65532" - Description: The default unprivileged user ID that all containers run as.
- Usage: Referenced by the
{{ set_user() }}macro whenuser: defaultis specified (or whenuser:is omitted, asdefaultis the default value). - Value:
65532is chosen as a high, non-conflicting UID that:- Avoids conflicts with system users (typically < 1000)
- Avoids conflicts with regular users (typically 1000-60000)
default_distros
- Type: Array of strings
- Default:
["rawhide", "hummingbird"] - Description: Defines the distribution variants (distros) for which images are built. Each distro represents a different base package source.
- Directory Structure: Images are organized as
images/<name>/<distro>/<variant>/ - Common Values:
rawhide- Uses Fedora Rawhide repositories onlyhummingbird- Uses both Fedora Rawhide and Hummingbird repositories
- Usage: Scripts iterate over distros and variants to find all image variants to build. Each distro/variant combination produces a separate container image.
- See Also: default_variant_repos for repository configuration per distro
default_variants
- Type: Array of strings
- Default:
["default", "builder"] - Description: Defines the default set of variants to generate for each
image within each distro when not explicitly specified in the image’s
properties.yml. - Directory Structure: Variants are subdirectories within each distro:
images/<name>/<distro>/<variant>/ - Common Values:
default- The minimal runtime variantbuilder- Extended variant with build tools and package managers
- Extending: Images can use
additional_variantsinproperties.ymlto add extra variants while keeping the defaults (preferred approach) - Override: Images can specify
variantsinproperties.ymlto replace these defaults entirely (use when you need to exclude default variants) - See Also: Image Configuration Reference - variants
default_rpm_packages
-
Type: Object with variant names as keys, arrays of package names as values
-
Default:
default_rpm_packages: builder: - bash - dnf5 - shadow-utils -
Description: Defines packages that are automatically included in specific variants across all images. These are added in addition to packages defined in image-specific
properties.yml. -
Variants: Currently only
builderis defined, but other variants can be added -
Usage: Use this to provide variant-specific packages that should be available in all images using that variant. For example, builder variants include package management tools for development workflows.
default_variant_repos
-
Type: Object with distro names as keys, arrays of repository filenames as values
-
Default:
default_variant_repos: rawhide: - fedora-44.repo hummingbird: - fedora-44.repo - hummingbird.repo -
Description: Defines which yum repositories each distro uses by default for package installation and lockfile generation. The Rawhide distro uses the latest branched Fedora development repo, while Hummingbird uses a stable Fedora release plus the Hummingbird package repository.
-
Repository Files: References files in the
yum-repos/directory -
Usage: This allows different distros to use different package sources. The
hummingbirddistro includes additional repositories that provide Hummingbird-specific packages. -
Override: Image-specific
additional_reposinproperties.ymlare appended to the distro-specific repos
oscap
-
Type: Object
-
Default:
oscap: enabled: true profiles: cis: true stig: variants: - "*fips*" exclude_rules: - id: xccdf_org.ssgproject.content_rule_root_path_no_dot reason: "False positive: environmentvariable58 probe requires /proc which is unavailable in offline chroot scanning" -
Description: Global defaults for OpenSCAP compliance scanning. Scanning is enabled by default (
enabled: true). Images can opt out by settingenabled: falsein theirproperties.yml. -
Fields:
enabled- Whether compliance scanning is active (overridden per image)profiles- Which compliance profiles run per variant. Each profile can betrue(all variants),false(disabled), or{variants: [...]}with glob patterns. Default: CIS for all variants, STIG for FIPS variants.exclude_rules- Global rule exclusions applied to all oscap-enabled images. Image-specific exclusions inproperties.ymlare concatenated with these (not replaced).
-
Merge Behavior: When an image overrides
oscapfields:enabled(scalar) is replaced by the image valueprofiles(dict) is recursively merged, so an image can override individual profiles without affecting othersexclude_rules(list) is concatenated, so image rules are added after global rules
-
See Also: Image Configuration Reference - Compliance Scanning
readme_targets
-
Type: Object with target names as keys, configuration objects as values
-
Default: See the actual file
-
Description: Defines target-specific configuration for generating multiple README files from a single template. Each target represents a different build/distribution channel (e.g., Hummingbird project vs Red Hat product).
-
Target Configuration Fields:
filename- Output filename for this target’s READMEregistry- Container registry URL for image referencesproduct_name- Full product name for documentationproduct_name_short- Short product name for headings and titlesdoc_base_url- Base URL for documentation links
-
Usage: Templates access these values via
{{ readme_targets[target].registry }}and similar expressions. The build system generates one README file per target from the sameREADME.md.j2template. -
See Also: Image Pipeline - README Generation
Next Steps
- Image Configuration Reference - Per-image configuration
- Image Variants - Understanding variant types
- Adding Images - How to add a new container image
3 - Konflux Resource Deployment
Konflux resources for container images are split between two repositories:
- containers: Per-image resources generated from
properties.yml - infrastructure: Application-level resources and deployment to the Konflux cluster
This separation allows independent iteration on each concern.
Deployment Matrix
| Resource Type | Source | Containers MR | Containers Push | Infra MR | Infra Push |
|---|---|---|---|---|---|
| Component | containers | manual | manual | - | - |
| ImageRepository | containers | manual | manual | - | - |
| ReleasePlanAdmission | containers | - | automatic | manual | automatic |
| EnterpriseContractPolicy | containers | - | automatic | manual | automatic |
| Application | infrastructure | - | - | manual | automatic |
| ReleasePlan | infrastructure | - | - | manual | automatic |
| IntegrationTestScenario | infrastructure | - | - | manual | automatic |
| ServiceAccount, Secret | infrastructure | - | - | manual | automatic |
Legend:
- Containers MR: Downstream pipeline triggered from containers MR (deploys from MR commit)
- Containers Push: Downstream pipeline triggered from containers push to main
- Infra MR: Infrastructure MR pipeline (manual trigger)
- Infra Push: Infrastructure push to main or web pipeline
Resource Locations and Rationale
Containers Repo
Resources are defined in konflux-templates/ and rendered to konflux-templates/rendered.yml.
Component and ImageRepository:
- Ownership: Components are managed by the containers repo. The infrastructure pipeline uses
ONLY_DOWNSTREAMso only explicit downstream triggers from containers deploy changes, giving the containers repo full control over the component lifecycle. - Dynamic generation: Generated from
images/*/properties.ymlviaci/internal/generate_konflux_resources.sh, depending on per-image configuration (variants, tags, repository names). - Timing: Must be deployed early during MR review so Konflux can build and test new images.
ReleasePlanAdmission:
- Dynamic generation: Includes per-image tag mappings extracted from
properties.yml. - Main branch only: Prevents race conditions—deploying from a feature branch risks undoing other images’ onboarding. If branch A adds image-foo and branch B (created earlier) adds image-bar, deploying B’s RPA would remove image-foo from the release config.
EnterpriseContractPolicy:
- Documentation co-location: Policy exclusions are documented in Image Pipeline. Keeping policy and documentation together eases maintenance.
Infrastructure Repo
Application, ReleasePlan, and IntegrationTestScenario are defined in
kubernetes/containers-*/ directories (one per Konflux application):
- Independent iteration: Changes are decoupled from containers repo activity—they can be modified, test-deployed via manual trigger in an infrastructure MR, verified, and merged without touching the containers repo.
- Testable before merge: If defined in the containers repo, these would only deploy after merging to main (like RPAs), making iteration difficult.
- Static configuration: These resources don’t depend on per-image data from
properties.yml.
ServiceAccount and Secret for releases are defined in
kubernetes/release-quay-hummingbird*/ directories (one per Quay organization):
- Security: Secret specifications (names, structure, credential references) should not be exposed in the containers repo.
- Independent iteration: Like other infrastructure resources, these can be modified and test-deployed without touching the containers repo.
The service accounts are referenced by ReleasePlanAdmissions to authorize pushing images to
each quay.io/hummingbird* organization.
See Also
- Image Pipeline – Build, test, and release stages
4 - Security Labels and Metadata
Hummingbird container images include labels, embedded metadata, and SBOMs that enable security scanners to perform container-first vulnerability reporting.
Overview
Security scanners need a way to determine which vulnerabilities are applicable to a container image. Three mechanisms provide this information:
- Container Labels - OCI image labels (
name,cpe) set in the Containerfile - Embedded Metadata - A
labels.jsonfile written to the container filesystem during build - Software Bill of Materials (SBOM) - An SPDX document listing all packages, attached as an OCI artifact alongside the image
Labels and embedded metadata identify the product; the SBOM identifies the packages within it. Labels and embedded metadata provide the same core information but serve different access paths: labels are accessible via container inspection tools, while embedded metadata is accessible to scanners with only filesystem access.
Applicability
The cpe label is only added to released Hummingbird distro images (no
CPE identifier for non-product images). All other labels are added to all
images.
The labels.json embedded metadata file contains all Containerfile LABEL
values plus auto-computed fields.
SBOMs are attached to all released images (both Rawhide and Hummingbird).
Container Labels
name
- Type: String
- Format:
<org>/<image>or<org>/<image>-<variant> - Examples:
hummingbird/nodejs-24,hummingbird/nodejs-24-builder,hummingbird/caddy - Description: Canonical name for the container image. This is the name that appears in VEX (Vulnerability Exploitability eXchange) statements and is used by scanners to map vulnerabilities to specific images.
The canonical name is derived from the image directory name (which includes
the version, e.g. nodejs-24) and the variant. For default variants, the
name is hummingbird/<image> (e.g. hummingbird/nodejs-24). For non-default
variants, the variant is appended with a hyphen (e.g.
hummingbird/nodejs-24-builder). Note that the canonical name may differ
from the registry repository path. For example, nodejs-24 and nodejs-20
are both pushed to the hummingbird/nodejs registry repository with
different tags, but their canonical names are hummingbird/nodejs-24 and
hummingbird/nodejs-20 respectively.
See container-image-labels.md for the full
name label format across all image types.
cpe
- Type: String (CPE 2.2 formatted)
- Format:
cpe:/a:redhat:hummingbird:1 - Description: Common Platform Enumeration (CPE) identifier for the container. CPE is a standardized naming scheme for software products that enables correlation with vulnerability databases.
The CPE value is defined globally in images/variables.yml and applied to all
Hummingbird images. Containers with the same CPE are considered the same
software product for vulnerability reporting purposes.
org.opencontainers.image.created
- Type: String (RFC3339 timestamp)
- Format:
2025-01-15T12:00:00Z - Description: Creation timestamp of the container image. Used by scanners to determine if an image predates or postdates a vulnerability fix.
If SOURCE_DATE_EPOCH is set during the build, that timestamp is used instead
of the actual build time, supporting reproducible builds.
Embedded Metadata (labels.json)
The labels.json file is written to /usr/share/buildinfo/labels.json inside
the container filesystem by the inject-source-info script during build. It
contains all Containerfile LABEL values plus auto-computed fields like
architecture and org.opencontainers.image.created.
Schema
The file follows the embedded_metadata.v1 schema published by Red Hat Product Security.
Fields
The embedded_metadata.v1 schema defines the minimum fields required by security scanners:
| Field | Type | Description |
|---|---|---|
name |
string | Canonical container name (e.g., hummingbird/nodejs-24) |
cpe |
string | CPE identifier (e.g., cpe:/a:redhat:hummingbird:1) |
architecture |
string | Target architecture (e.g., amd64, arm64) |
org.opencontainers.image.created |
string | RFC3339 creation timestamp |
The file also includes all other Containerfile LABEL values (e.g.,
description, summary, vendor, version). See
container-image-labels.md for the complete
label reference.
Example
{
"name": "hummingbird/caddy",
"cpe": "cpe:/a:redhat:hummingbird:1",
"architecture": "amd64",
"org.opencontainers.image.created": "2025-01-15T12:00:00Z"
}
Software Bill of Materials (SBOM)
Each released image has a per-architecture SPDX 2.3 SBOM attached as an OCI artifact. See SBOM Generation for how the production SBOMs are built from Syft, Hermeto, and Mobster.
Accessing SBOMs
SBOMs are stored as OCI artifacts alongside each per-architecture image. To download one, resolve the per-arch digest and use cosign:
IMAGE="quay.io/hummingbird/caddy"
TAG="latest"
ARCH="amd64"
# Get per-architecture digests from the image index
skopeo inspect --raw "docker://${IMAGE}:${TAG}" \
| jq '.manifests[] | {digest, platform}'
# Download the SBOM for the chosen architecture
ARCH_DIGEST="$(skopeo inspect --raw "docker://${IMAGE}:${TAG}" \
| jq -r --arg arch "${ARCH}" \
'.manifests[] | select(.platform.architecture == $arch) | .digest')"
cosign download sbom "${IMAGE}@${ARCH_DIGEST}" > sbom.json
Entry Sources
The merged SBOM contains entries from two tools:
| Source | Content | Scope |
|---|---|---|
| Syft | Installed binary RPMs + non-RPM packages | Image architecture only |
| Hermeto | Build-time dependencies from lockfiles | All architectures + source RPMs |
A package installed in the final image may have entries from both Syft and Hermeto (with different metadata), since it was both a build dependency and is present at runtime.
Distinguishing Entry Sources
After the Mobster merge, Syft and Hermeto entries carry mutually exclusive markers:
| Marker | Syft | Hermeto |
|---|---|---|
sourceInfo field present |
✓ | |
licenseDeclared set |
✓ | |
| CPE references | ✓ | |
upstream= in PURL |
✓ | |
| Annotation containing “hermeto” | ✓ | |
repository_id= in PURL |
✓ |
As a general rule, any entry with an annotation containing the word “hermeto” (case-insensitive) originates from Hermeto (build-time provenance). All other entries originate from Syft (runtime image scan).
PURL Format
Syft and Hermeto use different PURL qualifier sets for the same package. Examples from caddy:latest (Hummingbird, amd64):
Syft (runtime):
pkg:rpm/hummingbird/caddy@2.10.2-1.hum1?arch=x86_64&distro=hummingbird-20251124&upstream=caddy-2.10.2-1.hum1.src.rpm
Hermeto (build, binary):
pkg:rpm/caddy@2.10.2-1.hum1?arch=x86_64&checksum=sha256:ca02a0...&repository_id=public-hummingbird-x86_64-rpms
Hermeto (build, source):
pkg:rpm/caddy@2.10.2-1.hum1?arch=src&checksum=sha256:42912d...&repository_id=public-hummingbird-source-rpms
Key differences:
- Distro namespace: Syft includes the distro in the PURL path
(
pkg:rpm/hummingbird/...). Hermeto omits it (pkg:rpm/...). - EVR: Syft populates
versionInfowith the full epoch:version-release. Hermeto setsversionInfoto the bare upstream version; the full EVR is only in the PURL@version. - Architecture: Syft entries match the image architecture. Hermeto entries
carry an
arch=PURL qualifier that may be the image arch, a different arch (cross-arch),noarch, orsrc(source RPMs). - Source RPM: Syft entries carry
upstream=<srpm>in the PURL. Hermeto entries have separatearch=srcentries instead.
Hermeto Annotation Format
Hermeto entries carry annotations with JSON-encoded metadata:
{
"annotationDate": "2026-03-02T11:58:12Z",
"annotationType": "OTHER",
"annotator": "Tool: hermeto:jsonencoded",
"comment": "{\"name\": \"hermeto:found_by\", \"value\": \"hermeto\"}"
}
Entry Breakdown Example
Typical counts for caddy:latest (Hummingbird, amd64); exact counts vary as image dependencies change:
| Source | Typical count | Content |
|---|---|---|
| Syft | ~53 | Binary RPMs (x86_64 + noarch) |
| Syft | 1 | Go module (stdlib) |
| Syft | 2 | OCI image metadata |
| Hermeto | ~41 | Binary RPMs (x86_64) |
| Hermeto | ~41 | Binary RPMs (aarch64, cross-arch) |
| Hermeto | ~11 | Binary RPMs (noarch) |
| Hermeto | ~36 | Source RPMs (arch=src) |
How Scanners Use This Metadata
- CPE Matching: Scanners use the
cpevalue to look up applicable VEX statements for the product - Name Matching: The
nameidentifies which specific container the VEX statements apply to - Version Comparison: The creation timestamp enables comparison between the scanned image and fixed versions reported in VEX statements
- Package Enumeration: Scanners use the SBOM to enumerate all packages in the image and correlate them with vulnerability databases via PURLs and CPEs
Related Files
| File | Purpose |
|---|---|
documentation/background/container-image-labels.md |
Complete reference for all image labels |
documentation/background/image-pipeline.md |
SBOM generation pipeline (Stage 3) |
images/variables.yml |
Defines the global cpe value |
images/hummingbird-builder/inject-source-info.sh |
Script that creates labels.json |
macros/inject_source_info_labels.yml.j2 |
Macro that adds LABEL to Containerfile |
macros/install_newroot.yml.j2 |
Macro that invokes inject-source-info |
See Also
- Container Image Labels – complete reference for all image labels across all categories
- Image Pipeline – SBOM Generation – how the production SBOMs are built from Syft, Hermeto, and Mobster
References
- Embedded Metadata Schema
- Container-First Vulnerability Reporting (Konflux feature specification)
- VEX (Vulnerability Exploitability eXchange)
- Syft – SBOM generation tool
- Hermeto – build-time dependency provenance tool
- Mobster – SBOM merging tool
- SPDX Specification
5 - Container Image Labels
Hummingbird container images carry labels from multiple standards and namespaces. This page is the single reference for every label.
Standards: C = Conforma (rule dataset), O = OCI Image Spec, H = Hummingbird project, S = Security schema
Labels
| Label | Aliases | Value | C | O | H | S |
|---|---|---|---|---|---|---|
architecture |
Host architecture (e.g., x86_64) |
✓ | ||||
com.redhat.component |
hummingbird |
✓ | ||||
com.redhat.license_terms |
UBI EULA ¹ | |||||
cpe |
CPE identifier (Hummingbird only) ² | ✓ | ✓ | |||
distribution-scope |
public |
✓ | ||||
io.hummingbird-project.containerfile |
Containerfile path relative to repo root ⁹ | ✓ | ||||
io.hummingbird-project.deprecated |
true for deprecated image streams |
✓ | ||||
io.hummingbird-project.major-minor-version |
Major.minor from tags (e.g., 2.10) ³ |
✓ | ||||
io.hummingbird-project.major-version |
Major from tags (e.g., 2) ³ |
✓ | ||||
io.hummingbird-project.repository |
Publishing name (e.g., caddy) ⁴ |
✓ | ||||
io.hummingbird-project.stream |
Version stream (e.g., 2) ⁴ |
✓ | ||||
io.hummingbird-project.variant |
Variant name (e.g., fpm-builder) ⁸ |
✓ | ||||
io.hummingbird-project.variant.base |
Base specialization (e.g., fpm) ⁸ |
✓ | ||||
io.hummingbird-project.variant.builder |
true when builder (absent otherwise) ⁸ |
✓ | ||||
io.hummingbird-project.variant.description |
Base variant description (no modifiers) ⁸ | ✓ | ||||
io.hummingbird-project.variant.fips |
true when FIPS (absent otherwise) ⁸ |
✓ | ||||
io.k8s.description |
Long description ⁵ | ✓ | ||||
maintainer |
Project Hummingbird / Red Hat |
✓ | ||||
name |
Canonical publishing repository ² | ✓ | ✓ | |||
org.opencontainers.image.created |
build-date |
RFC3339 build timestamp | ✓ | ✓ | ✓ | |
org.opencontainers.image.description |
description |
Long description ⁵ | ✓ | ✓ | ||
org.opencontainers.image.revision |
vcs-ref |
Full git commit SHA | ✓ | ✓ | ||
org.opencontainers.image.source |
GitLab repository URL | ✓ | ||||
org.opencontainers.image.title |
Image name (e.g., caddy) |
✓ | ||||
org.opencontainers.image.url |
url |
Upstream project URL ⁵ | ✓ | ✓ | ||
org.opencontainers.image.vendor |
vendor |
Red Hat, Inc. ⁶ |
✓ | ✓ | ||
org.opencontainers.image.version |
version |
Full version from tags (e.g., 2.10.2) ³ |
✓ | ✓ | ||
release |
SOURCE_DATE_EPOCH (commit timestamp) ⁷ |
✓ | ||||
summary |
Short one-liner ⁵ | ✓ | ||||
vcs-type |
git |
✓ |
Notes
¹ Licenses
The com.redhat.license_terms label is not required by Conforma, OCI, or
the security schema. It is a Red Hat convention present on all Red Hat
container images (UBI, language runtimes, etc.), pointing to the UBI EULA.
The org.opencontainers.image.licenses label is intentionally not set.
Per-package license data is derived from
SBOMs
stored as OCI artifacts alongside each image. A manually-set SPDX expression
would be incomplete compared to the SBOM-derived data available in the image
catalog.
² Name and identity
The name label identifies the canonical publishing repository. It uses the
repository value from properties.yml, prefixed by the registry organization:
- Red Hat supported Hummingbird images:
hi/<repository> - Community images:
hummingbird-community/<repository> - Experimental images:
hummingbird-ci/<repository> - Rawhide images:
hummingbird-rawhide/<repository>
All variants published to the same repository have the same name label. For
example, the default and builder variants of Caddy both use hi/caddy.
The cpe label is only set for Hummingbird distro images (not Rawhide).
See Security Labels and Metadata for
details on the scanning workflow and labels.json.
³ Version labels
Three version granularities are derived from the image’s tags:
version (full, e.g., 2.10.2), major-minor-version (e.g., 2.10),
and major-version (e.g., 2). The full version is the OCI/Conforma
org.opencontainers.image.version; the two coarser granularities are
Hummingbird project labels used for tag aliasing. There is no
io.hummingbird-project.version label — it would duplicate the OCI label.
⁴ Repository and stream
The io.hummingbird-project.repository and io.hummingbird-project.stream
fields together form the release identity pair. Both are defined in
properties.yml. See
image-configuration-reference.md
for field definitions.
The io.hummingbird-project.deprecated label is present with the value true on
the final release of a deprecated stream. It is omitted for non-deprecated streams. The
catalog uses the label to preserve the current deprecation state without changing
older image digests.
⁵ Description, summary, and URL
The description, summary, and url fields are defined in
properties.yml. Description and summary target different display
contexts:
- summary: One-liner (~40-80 chars) for table/list views
- description: Short paragraph (~100-250 chars, 1-2 sentences) for
card views and
podman inspect
Style rules:
- Do not start
summaryordescriptionwith the image name - Use
>-YAML scalar for multi-line readability inproperties.yml - Avoid embedded double quotes and backslashes (no escaping in templates)
⁶ Vendor
All images use Red Hat, Inc. as vendor (the distributing entity).
The OCI org.opencontainers.image.vendor uses Red Hat (without
“, Inc.”) per OCI convention. Both values are set on all distros.
⁷ Release
The release label uses the commit timestamp as a Unix epoch (matching
Red Hat convention). In Konflux this is set via the generate-labels
pipeline task.
⁸ Variant labels
Each variant name is decomposed into a base specialization and
cross-cutting modifiers (builder, fips). The naming convention is
<base>[-fips][-builder], where modifier order does not matter. A bare
modifier like builder has base default.
Examples: fpm-builder → base=fpm, builder=yes; fips-builder →
base=default, builder=yes, fips=yes.
The .description label stores only the base description (e.g.,
“PHP FastCGI process manager”). Modifier display is handled by
consumers using the .builder and .fips boolean labels. Base
descriptions come from variant_descriptions in images/variables.yml
(for default) and image-specific properties.yml (for bases like
fpm, runtime).
⁹ Containerfile path
The io.hummingbird-project.containerfile label contains the path to the
Containerfile source relative to the repository root (e.g.,
images/caddy/hummingbird/default/Containerfile). A full URL to the
source can be constructed from org.opencontainers.image.source +
org.opencontainers.image.revision + this path.
Embedded Metadata (labels.json)
All labels are written to /usr/share/buildinfo/labels.json inside the
container filesystem, providing filesystem-level access for security
scanners. See Security Labels and Metadata
for schema details.
Related Files
| File | Purpose |
|---|---|
images/<image>/properties.yml |
Per-image metadata fields |
ci/internal/generate_jinja2.py |
Builds inject_labels dict from properties |
macros/image_metadata_labels.yml.j2 |
Emits LABEL instructions from inject_labels |
macros/inject_source_info_labels.yml.j2 |
Emits name and cpe LABEL instructions |
macros/install_newroot.yml.j2 |
Invokes inject-source-info.sh with labels |
images/hummingbird-builder/inject-source-info.sh |
Writes labels.json to container filesystem |
ci/build_images.sh |
Adds build-time labels |
ci/check_release_fields.py |
Validates label field values in properties.yml |
6 - CI Scripts
This section documents the CI scripts used in the Project Hummingbird container build and test pipeline.
These scripts can be run locally for development and testing, and are also used by the automated CI/CD pipeline.
6.1 - build_images.sh
Purpose
Build container images using buildah with support for multiple architectures and container engines.
Usage
Usage: ci/build_images.sh [OPTIONS] [GROUP_NAMES...] [-- BUILDAH_ARGS...]
OPTIONS:
--verbose, -v Enable verbose output during build
--arch ARCHITECTURE Specify target architecture (e.g., amd64, arm64, arm/v7)
--engine ENGINE Specify runtime engine for testing/export (podman or docker)
--setup Set up Docker-in-Docker environment before exporting
--component-name NAME
Parse component name (format: group--distro--variant)
Example: curl--rawhide--default → curl/rawhide/default
--local-rpms-dir DIR Directory containing custom RPMs to use during build.
These have a higher priority and thus override the
standard repositories.
--build-deps Build all dependencies of specified groups, but NOT the
specified groups themselves. Collects forward, reverse,
and transitive dependencies automatically.
--pull Pull images from registry instead of building locally.
Fast alternative for local development. Uses published
images from quay.io.
--dryrun Show what would be built (or pulled with --pull) without
actually building/pulling.
--help, -h Show this help message
Examples
# Build single image group (all distro/variants)
ci/build_images.sh nginx
ci/build_images.sh nodejs-20
# Build specific distro/variant only
ci/build_images.sh nginx/rawhide/builder
ci/build_images.sh nodejs-20/hummingbird/default
# Build multiple image groups at once
ci/build_images.sh nginx curl git
ci/build_images.sh nginx/rawhide/default curl/hummingbird/builder
# Build with options
ci/build_images.sh --verbose curl
ci/build_images.sh --arch arm64 nginx
ci/build_images.sh --engine docker nginx
ci/build_images.sh --engine podman --verbose nginx
# Build multiple image groups with options
ci/build_images.sh --verbose nginx curl git
ci/build_images.sh --arch arm64 nginx postgresql
# Build all dependencies of dotnet-runtime-10-0 (forward + reverse + transitive)
ci/build_images.sh --build-deps dotnet-runtime-10-0
# Pull dependencies from registry instead of building (faster for local dev)
ci/build_images.sh --build-deps --pull dotnet-runtime-10-0
# Pull a specific image from registry
ci/build_images.sh --pull nginx/rawhide/default
# Build from CI component name format (used in CI environments)
ci/build_images.sh --component-name curl--rawhide--default
# Build with custom RPMs (for testing modified packages)
ci/build_images.sh --local-rpms-dir ../rpms/builds/hostname/RPMS git/rawhide/builder
Note: Some images require git submodules initialized (use
git init --recurse-submodules when cloning or git submodule update --init if
already cloned). When building for foreign architectures, make sure qemu-user-static
is available.
Dependency Building
The --build-deps flag builds all dependencies needed for testing an image, but not
the image itself. This is used in CI to ensure all required images are available
before running tests.
Pulling vs Building Dependencies
For local test development, use --pull with --build-deps to pull pre-built
images from the registry instead of building them locally. This is much faster
and uses the same dependency collection logic:
# Slow: Build all dependencies locally
ci/build_images.sh --build-deps nginx
# Fast: Pull all dependencies from registry
ci/build_images.sh --build-deps --pull nginx
The --pull flag works without --build-deps too:
# Pull a specific image instead of building it
ci/build_images.sh --pull caddy/rawhide/default
How Dependency Collection Works
When you run ci/build_images.sh --build-deps <image>, it performs a 2-level expansion:
-
Level 1: Collects forward and reverse dependencies of the specified image
- Forward dependencies: Images that the specified image’s tests depend on
(detected by
TEST_IMAGES[...]references in test files) - Reverse dependencies: Images that depend on the specified image
(filtered by
reverse_dependency_tests: trueinproperties.yml)
- Forward dependencies: Images that the specified image’s tests depend on
(detected by
-
Level 2: Collects forward dependencies of the reverse dependencies
-
Stops: No further expansion (avoids infinite graph traversal)
All dependencies are automatically deduplicated to ensure each image is built exactly once.
Relationship to Testing
The reverse_dependency_tests property in properties.yml affects both building and testing:
- Build phase (
--build-deps): Filters which reverse dependencies to build - Test phase (
ci/run_tests_container.shandci/run_tests_k8s.shwith--include-reverse-deps): Filters which reverse dependencies to test
Set reverse_dependency_tests: false for images like curl that are used pervasively
but don’t need reverse dependency workflows.
Dependency Building Examples
# Build dependencies for dotnet-runtime-10-0
# Builds: dotnet-sdk-10-0 (forward dependency)
ci/build_images.sh --build-deps dotnet-runtime-10-0
# Pull dependencies for dotnet-runtime-10-0 (faster alternative)
# Pulls: dotnet-sdk-10-0 from quay.io/hummingbird-rawhide
ci/build_images.sh --build-deps --pull dotnet-runtime-10-0
# Build dependencies for core-runtime
# Builds: xcaddy, go, rust (reverse deps with reverse_dependency_tests: true)
# Plus their forward dependencies
ci/build_images.sh --build-deps core-runtime
# Use --dryrun to see what would be built/pulled without actually doing it
ci/build_images.sh --dryrun --build-deps core-runtime
ci/build_images.sh --dryrun --build-deps --pull core-runtime
Building with custom RPMs
The --local-rpms-dir option enables testing container images with
custom-built RPM packages, for iterating on package changes before committing
to the RPM repository.
Workflow
-
Build custom RPMs in the
rpmsrepository (see its documentation for the complete workflow):cd ../rpms # modify a package ci/build_rpms.sh packagename # Built RPMs will be in builds/packagename/RPMS/ -
Build container image using the custom RPMs:
cd ../containers ci/build_images.sh --local-rpms-dir ../rpms/builds/packagename/RPMS imagename/builder -
Verify the custom package was installed:
podman run --rm --entrypoint '' quay.io/hummingbird/imagename:latest-builder rpm -qa
6.2 - run_tests_container.sh
Purpose
Run tests for image groups using containerized test environment with support for both Docker and Podman engines.
Usage
Run ci/run_tests_container.sh --help for full usage information.
Usage: ci/run_tests_container.sh [OPTIONS] [GROUP_NAMES...]
OPTIONS:
--verbose, -v Enable verbose output during testing
--engine ENGINE Specify container engine (podman or docker)
--setup Set up Docker-in-Docker environment before running tests
--pause, -p Pause failed tests before cleanup to allow debugging
Prints a message and waits for Enter before cleaning up containers
--hermetic Use hermetic builds (--pull=never, only use prefetched/built images)
Default for CI. Without this, missing images are pulled on demand.
--component-name NAME
Parse component name (format: group--distro--variant)
Example: curl--rawhide--default → curl/rawhide/default
--group-component-name NAME
Parse component name and add /group variant
Example: curl--rawhide--default → curl/rawhide/group
--include-reverse-deps
Also test groups that depend on specified groups
--help, -h Show this help message
Note: If no distro/variant is specified, all combinations will be tested. To test
only a specific variant, use <group_name>/<distro>/<variant>. To test only a
specific test, use <group_name>/<distro>/<variant>/<test>.
Engine Selection: Use --engine to specify the container engine (podman
or docker). When using Docker, add --setup for automatic Docker-in-Docker
environment setup.
Hermetic vs Non-Hermetic Testing:
- Local development (default): Without
--hermetic, the test runner allows pulling missing images from the registry. This is convenient for testing individual images without building all dependencies first. - CI environment: Use
--hermeticto enforce that tests only use prefetched or locally-built images (via--pull=never). This ensures reproducible builds and prevents accidentally using images from the registry that differ from what Konflux built.
Building and Testing
For local development, you’ll typically want to build the images first, then test them:
# Build the images
ci/build_images.sh <group_name>[/distro/variant]
ci/build_images.sh <group_name1> <group_name2> # Build multiple image groups
# Test the images
ci/run_tests_container.sh <group_name>[/distro/variant]
ci/run_tests_container.sh <group_name1> <group_name2> # Test multiple image groups
The build script uses the same syntax as the test script, making it easy to build and test the same image/variant combination.
When working on base images (like core-runtime) that other images depend on,
build the base image and its dependencies before running reverse-dependency tests:
# Build core-runtime, then its forward and reverse dependencies
ci/build_images.sh core-runtime
ci/build_images.sh --build-deps core-runtime
# Then test them all
ci/run_tests_container.sh --include-reverse-deps core-runtime
The -p/--pause option pauses the script on failed tests before cleaning up
to allow interactive and efficient debugging.
Testing with Specific Image Builds
To reproduce CI failures, you can run the test with mapping the image under test to the Konflux build. Set IMAGE_URL_<GROUP>__<DISTRO>__<VARIANT> environment variables (uppercase, hyphens/dots → underscores, __ separates parts):
# Test with a specific CI build
IMAGE_URL_TOMCAT_10__HUMMINGBIRD__BUILDER='quay.io/redhat-user-workloads/.../tomcat-10--hummingbird--builder@sha256:...' \
ci/run_tests_container.sh tomcat-10/hummingbird/builder
Automatic Retries for Transient Infrastructure Failures
The test runner automatically retries tests that fail with certain transient infrastructure errors. This helps avoid false test failures caused by temporary issues with external services like container registries or network problems.
Retry Behavior:
- Failed tests are checked against a list of retriable error patterns
- If a match is found, the test is automatically retried
- If the test still fails after all attempts, it’s reported as a normal failure
Examples
# Test single group (all distro/variants)
ci/run_tests_container.sh curl
ci/run_tests_container.sh nginx
# Test single group (specific distro/variant)
ci/run_tests_container.sh curl/rawhide/default
ci/run_tests_container.sh nodejs-20/hummingbird/builder
# Test multiple groups (all distro/variants)
ci/run_tests_container.sh curl nginx mariadb
# Test multiple groups (mixed variants)
ci/run_tests_container.sh curl/rawhide/default nginx mariadb/hummingbird/builder
# Test with different engines
ci/run_tests_container.sh --engine docker --setup git
ci/run_tests_container.sh --engine podman --verbose nginx
# Test with verbose output for debugging
ci/run_tests_container.sh --verbose nginx mariadb dotnet-runtime-8-0
# Test specific tests
ci/run_tests_container.sh curl/rawhide/default/version
ci/run_tests_container.sh nginx/rawhide/default/port
ci/run_tests_container.sh curl/rawhide/default/version nginx/hummingbird/default/port
# Also test groups with reverse dependencies
ci/run_tests_container.sh --include-reverse-deps core-runtime
# Test from CI component name format (used in CI environments)
ci/run_tests_container.sh --component-name curl--rawhide--default
# Test across variants from CI component name format
ci/run_tests_container.sh --group-component-name curl--rawhide--default
Reverse dependency testing is enabled by default for all images. When enabled, changes to an image will automatically:
- Find images that depend on it by scanning for
TEST_IMAGES[group/distro/variant]references - Run tests for both the changed image and all its dependents
This catches breaking changes in base images early. Images can opt-out by setting
reverse_dependency_tests: false in properties.yml (e.g., curl, which is
widely used but typically doesn’t need reverse dependency testing).
6.3 - run_tests_k8s.sh
Purpose
Run K8s tests for image groups using kubectl. Tests execute in a real Kubernetes environment, validating that images work correctly in Kubernetes.
Usage
Run ci/run_tests_k8s.sh --help for full usage information.
Usage: ci/run_tests_k8s.sh [OPTIONS] [GROUP_NAMES...]
OPTIONS:
--context CONTEXT K8s context to use (REQUIRED unless --kubeconfig set)
--kubeconfig FILE Path to kubeconfig file (REQUIRED unless --context set)
--push-image IMAGE Push local image to OpenShift internal registry before testing
Sets TEST_IMAGE to the internal registry reference
--verbose, -v Show full output for passing tests
--pause, -p Pause on test failure before cleanup to allow debugging
--component-name NAME
Parse component name (format: group--distro--variant)
Example: curl--rawhide--default → curl/rawhide/default
--group-component-name NAME
Parse component name and add /group variant
Example: curl--rawhide--default → curl/rawhide/group
--include-reverse-deps
Also test groups that depend on specified groups
--output FILE Write JSON test results to FILE (for CI integration)
When set, exits 0 after writing results regardless of test outcome
--help, -h Show this help message
Safety: The script requires explicit --context or --kubeconfig to prevent
accidental operations on production clusters.
Note: If no distro/variant is specified, all combinations will be tested. To test
only a specific variant, use <group_name>/<distro>/<variant>. To test only a
specific test, use <group_name>/<distro>/<variant>/<test>.
Local Development Workflow
For local development, build images and push them to the internal registry:
# Build the image locally
podman build -t my-nginx:dev images/nginx/hummingbird/default/
# Push to internal registry and test
ci/run_tests_k8s.sh --context mpp-preprod --push-image my-nginx:dev nginx/hummingbird/default
The --push-image flag:
- Compares local image digest with remote
- Skips push if image already exists with same digest
- Uses port-forward to registry-proxy for pushing
- Sets
TEST_IMAGEto the internal registry reference
Prerequisites:
oc loginto the target clusteroc project <namespace>to set the target namespace- Port-forward access to
registry-proxyinhummingbird--internal
Testing with Published Images
Test published images without building locally:
# Test using published images (resolved via IMAGE_URL/IMAGE_NAME)
IMAGE_URL=quay.io/hummingbird/nginx:latest \
IMAGE_NAME=nginx--hummingbird--default \
ci/run_tests_k8s.sh --context mpp-preprod nginx/hummingbird/default
Environment Variables
Tests have access to these environment variables:
| Variable | Description |
|---|---|
TEST_IMAGE |
Container image under test (with digest) |
TEST_IMAGES |
Associative array with group/variant image URLs (uses TEST_DISTRO context) |
TEST_IMAGES_PATH |
Path to file containing serialized TEST_IMAGES array |
TEST_GROUP |
The image group being tested |
TEST_DISTRO |
The distro being tested (e.g., rawhide) |
TEST_VARIANT |
The variant being tested (e.g., default) |
TEST_VERBOSE |
Show test command output (true or false) |
TEST_RUN_ID |
Unique ID for this test run (for resource naming) |
TEST_RUN_LABEL |
Label selector for cleanup (hum-k8s-test=<id>) |
Cluster Access
The kubectl command is pre-configured with the context/kubeconfig from CLI args,
so tests can use it directly without additional configuration.
Helper Function
| Function | Description |
|---|---|
test_fail |
Fail the test with a custom error message |
Examples
# Test single group with explicit context (all distro/variants)
ci/run_tests_k8s.sh --context mpp-preprod nginx
# Test specific distro/variant
ci/run_tests_k8s.sh --context mpp-preprod nginx/hummingbird/default
# Test specific test
ci/run_tests_k8s.sh --context mpp-preprod nginx/hummingbird/default/readiness-probe
# Test with verbose output for debugging
ci/run_tests_k8s.sh --verbose --context mpp-preprod nginx
# Test with kubeconfig file (CI environments)
ci/run_tests_k8s.sh --kubeconfig /workspace/kubeconfig nginx/hummingbird/default
# Build locally and push to internal registry
podman build -t my-nginx:dev images/nginx/hummingbird/default/
ci/run_tests_k8s.sh --context mpp-preprod --push-image my-nginx:dev nginx/hummingbird/default
# Test from CI component name format
ci/run_tests_k8s.sh --kubeconfig /workspace/kubeconfig --component-name nginx--hummingbird--default
# Write JSON results for CI integration
ci/run_tests_k8s.sh --output /tmp/results.json --kubeconfig /workspace/kubeconfig nginx/hummingbird/default
Resource Cleanup
Tests should label resources with TEST_RUN_LABEL for automatic cleanup:
kubectl create configmap my-config --from-literal=key=value
kubectl label configmap my-config "${TEST_RUN_LABEL}"
The test runner automatically cleans up all resources with the test run label after each test and at script exit.
CI Integration
In CI environments (Konflux), the script receives:
--kubeconfigpointing to the ephemeral namespace kubeconfig--component-nameor--group-component-namefor component identification--outputfor structured JSON results
The --output flag writes results in Konflux-compatible format and exits 0,
allowing the pipeline to read results without relying on exit codes.
6.4 - retrigger_failed_checks.py
Purpose
Retrigger failed Konflux CI checks by posting retest commands and waiting for the checks to start running.
Usage
Run ci/retrigger_failed_checks.py --help for full usage information.
Usage: ci/retrigger_failed_checks.py <MR_URL> [OPTIONS]
OPTIONS:
-h, --help show this help message and exit
--dry-run just print what comments would be posted
--token-path, -t PATH path to file containing GitLab API token
Authentication (in order of precedence):
1. --token-path: Path to file containing GitLab API token (highest priority)
2. GITLAB_TOKEN_PATH: Environment variable with path to token file
3. GITLAB_TOKEN: Environment variable with GitLab API token (fallback)
Behavior
The script will:
- Extract failed pipeline runs from the commit statuses on the MR
- Generate
/retest {pipeline-name}commands for each failed run - Post the retest commands as MR comments (unless –dry-run)
- Wait for the pipeline runs to start (unless –dry-run)
Examples
# Using --token-path option (highest priority)
ci/retrigger_failed_checks.py --token-path /path/to/token https://gitlab.com/group/project/-/merge_requests/1234
# Using GITLAB_TOKEN_PATH environment variable
GITLAB_TOKEN_PATH=/path/to/token ci/retrigger_failed_checks.py https://gitlab.com/group/project/-/merge_requests/1234
# Using GITLAB_TOKEN environment variable (legacy)
GITLAB_TOKEN=your-token-here ci/retrigger_failed_checks.py https://gitlab.com/group/project/-/merge_requests/1234
# Dry-run mode (show what would be done, don't post or wait)
ci/retrigger_failed_checks.py --token-path /path/to/token --dry-run https://gitlab.com/group/project/-/merge_requests/1234
6.5 - gitlab_sync.py
Purpose
Sync a file from a public source URL to a target GitLab repository via merge
request. The script always waits for the MR to be merged and for the
post-merge pipeline to complete before returning. It is invoked by
infrastructure CI jobs to keep external repos (pyxis-repo-configs,
konflux-release-data) in sync with generated files from the source repos.
Usage
Run gitlab_sync.py --help for full usage information.
usage: gitlab_sync.py [-h] --source-url SOURCE_URL
--target-project TARGET_PROJECT
--target-file TARGET_FILE --sync-branch SYNC_BRANCH
--mr-title MR_TITLE --gitlab-url GITLAB_URL
[--merge-timeout MERGE_TIMEOUT]
[--post-merge-timeout POST_MERGE_TIMEOUT]
[--mr-description MR_DESCRIPTION] [--squash-on-merge]
[--dry-run]
Authentication:
GITLAB_TOKEN Environment variable with GitLab API token (required)
Monitoring:
SENTRY_DSN Sentry DSN for alerting on failures (optional)
How It Works
- Fetch source from the public
--source-url(with HTTP retries) - Determine comparison ref: use the sync branch if it exists, otherwise the project’s default branch
- Compare source content against the comparison ref (stripped whitespace)
- Early exit if content matches and the comparison ref is the default branch (nothing to deploy)
- Commit the source to the sync branch using the GitLab Commits API with
force: true(creates a single-commit branch on top of the default branch). Also auto-squashes if the branch has accumulated multiple commits. - Create or update MR targeting the default branch
- Self-approve the MR (best-effort; continues if already approved)
- Post takeover comment with
CI_JOB_IDfor preemption tracking - Wait for MR merge (polling every 30s, up to
--merge-timeout) - Wait for post-merge pipeline to succeed (up to
--post-merge-timeout)
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success (MR merged) or no changes needed |
| 1 | Failure (timeout, pipeline failure, unrecoverable error) |
| 42 | Preempted by a newer CI job managing the same MR |
Preemption
When multiple CI jobs target the same sync branch, the script uses MR
comments to coordinate. Each job posts a comment containing its CI_JOB_ID
(monotonically increasing within a GitLab instance). Before each merge
attempt, the script checks for comments with a higher job ID. If found, it
yields by exiting with code 42.
The caller should handle exit 42 to stop processing (a newer job will handle
all remaining syncs). When running outside CI (CI_JOB_ID not set),
preemption is disabled.
Error Recovery
- Pipeline failure: Retries the MR pipeline with exponential backoff (60s, 300s, 900s). Sends a Sentry alert if all retries are exhausted.
- Merge conflict: Rebases the MR and re-approves (rebase resets approvals in GitLab). Up to 3 attempts before failing with a Sentry alert.
- Post-merge pipeline failure: Retries once, then returns exit code 1. This blocks downstream sync steps (e.g., Pyxis must succeed before RPA).
- Draft MR: Keeps polling without attempting to merge (allows manual intervention).
Examples
# Dry run: show diff without modifying anything
GITLAB_TOKEN=$TOKEN gitlab_sync.py \
--source-url "https://gitlab.com/redhat/hummingbird/containers/-/raw/main/releng/pyxis-hummingbird.yaml" \
--target-project "releng/pyxis-repo-configs" \
--target-file "products/hummingbird/hummingbird.yaml" \
--sync-branch "hummingbird/sync-containers-pyxis" \
--mr-title "chore: Update hummingbird Pyxis config" \
--gitlab-url "https://gitlab.cee.redhat.com" \
--dry-run
# Sync Pyxis config (requires squash merge)
gitlab_sync.py \
--gitlab-url "https://gitlab.cee.redhat.com" \
--source-url "https://gitlab.com/redhat/hummingbird/containers/-/raw/main/releng/pyxis-hummingbird.yaml" \
--target-project "releng/pyxis-repo-configs" \
--target-file "products/hummingbird/hummingbird.yaml" \
--sync-branch "hummingbird/sync-containers-pyxis" \
--mr-title "chore: Update hummingbird Pyxis config" \
--squash-on-merge
# Sync RPM RPA to konflux-release-data
gitlab_sync.py \
--gitlab-url "https://gitlab.cee.redhat.com" \
--source-url "https://gitlab.com/redhat/hummingbird/rpms/-/raw/main/releng/hummingbird-rpms-staging.yaml" \
--target-project "releng/konflux-release-data" \
--target-file "config/kflux-prd-rh03.nnv1.p1/product/ReleasePlanAdmission/hummingbird/hummingbird-rpms-staging.yaml" \
--sync-branch "hummingbird/sync-rpms-rpa" \
--mr-title "Update hummingbird RPM ReleasePlanAdmission"
Preemption wrapper for sequential syncs in a CI job:
gitlab_sync.py --gitlab-url ... --source-url ... --squash-on-merge
rc=$?
if [ "$rc" -eq 42 ]; then
echo "Preempted by newer job, stopping"
exit 0
elif [ "$rc" -ne 0 ]; then
exit "$rc"
fi
# Proceed to next sync only on success
gitlab_sync.py --gitlab-url ... --source-url ...
Environment Variables
| Variable | Required | Description |
|---|---|---|
GITLAB_TOKEN |
Yes | API token with Developer+ access on the target project |
CI_JOB_ID |
No | Set automatically in CI; enables preemption detection |
SENTRY_DSN |
No | Sentry DSN for failure alerting |
Development
The script and gitlab-ci tool image are maintained in the
Hummingbird tools repository, not this repository.
Make implementation and test changes there.