Image Pipeline
Explanation of the complete container image pipeline, from source templates through generation, build, testing, and release.
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 Quay.io
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 Setting Up a Konflux Cluster guide for deployment procedures.
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
Stage 4: Testing
Testing Farm Integration
When a merge request modifies container images, automated integration tests run via Testing
Farm. Tests execute on RHEL-9-Nightly systems for both x86_64 and aarch64
architectures as configured in the infrastructure repository.
Test results appear as external jobs in the GitLab CI pipelines, alongside regular GitLab CI jobs. These external jobs provide pass/fail status and link to the Testing Farm PipelineRun in Konflux.
Testing Flow
- Developer opens MR modifying
images/nginx/ - GitLab CI pipeline and Konflux pipelines start in parallel, and Konflux jobs appear as external jobs in the GitLab CI pipeline
- 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
Test Triggering
Tests are only triggered on merge requests, not on main branch builds.
For a container 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, two arch-specific pipelines per variant will be created.
If there is more than one variant in an image group, two additional arch-specific group pipelines will be created. See Konflux group snapshot documentation for more details.
Each pipeline will run tests for Podman and Docker engines. Tests will be selected based on the
variants field in the group-specific tests-container.yml files. Additionally, global tests from
ci/{variant}-tests/tests-container.yml will also be included.
The group pipelines will run tests that specify variants: [group], allowing tests to validate
functionality across multiple variants.
For groups that have reverse dependency testing enabled (the default, unless reverse_dependency_tests: false is set in properties.yml), tests from dependent images are also included, with those images
being built locally in the testing environment to ensure compatibility with the code under test.
Integration Test Scenarios
Integration tests are triggered via IntegrationTestScenario resources defined in the
infrastructure repository:
| Application | Tests Images From | Purpose |
|---|---|---|
containers-main/ |
images/ |
Production user-facing images |
containers-ci-main/ |
ci/images/ |
Internal CI tooling images |
The integration tests use the upstream Testing Farm container testing 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}
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) |
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 - Download and install Docker CLI for Docker-in-Docker 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. We build the builder image first to ensure that changes to it are tested when rebuilding. - If needed, build reverse dependency images locally via Buildah
- Run Podman and Docker tests for the component under test 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 for the updated components via
ci/run_tests_container.sh --group-component-name
- Run Podman and Docker group tests for the updated components via
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.
Dependent images are rebuilt locally in the testing environment. This prevents version skew between container images and tests and ensures that the dependent images are in sync with the repository status in the merge request under test.
For example, when core-runtime changes:
- Konflux builds
core-runtimeimage - Testing Farm builds dependent images locally (
rust,xcaddy) - Tests run for
core-runtime(from Konflux) and dependent images (local builds)
Stage 5: Enterprise Contract Validation
Before images can be released to production, they must pass Enterprise Contract (also known as Conforma) policy validation. This ensures images meet security, compliance, and build quality standards.
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-main- Production user-facing image policiescontainers-ci-main- CI tooling image policies (includes additional exclusions passed via theadditional_exclusionsparameter inkonflux-resources.yml.j2)
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.
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.
The hermetic_task.hermetic check is excluded only for the containers-ci policy because the
gitlab-ci image sets hermetic: false. This image
requires network access during build to install npm packages globally (markdownlint-cli) and
download the latest grype/syft releases from the GitHub API. These dependencies are not currently
prefetched.
CVE Package (CI-Only)
The cve package checks for blocking and non-blocking CVEs in container images.
The cve.cve_blockers check is excluded only for the containers-ci policy because some CVEs
are unfixable until we publish our own VEX stream to mark them as “not affected” (see
MR !2227).
Stage 6: Release
After images pass testing in merge requests and are merged to main, they are released to production.
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 copied from development registry to production registry
- Tags are applied based on
properties.ymlconfiguration
Release Output
Production images are published to the hummingbird organization on Quay.io:
quay.io/hummingbird/<image-repository>:<image-tag>
Examples:
quay.io/hummingbird/curl:latestquay.io/hummingbird/git:latest-builderquay.io/hummingbird/nodejs:20(fromnodejs-20/)quay.io/hummingbird/nodejs:latest(fromnodejs-24/)
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)
Non-default variants receive a -<variant> suffix (e.g., latest-builder).
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.
Next Steps
- 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