Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 10 min read

From Podman to Production: Building Trusted Container Images with Konflux

I work at Red Hat as a Senior SRE on the Konflux Infra team. We are responsible for the infrastructure around Konflux CI, the build system Red Hat has been developing: the clusters it runs on, the Terraform behind them,

From Podman to Production: Building Trusted Container Images with Konflux

I work at Red Hat as a Senior SRE on the Konflux Infra team. We are responsible for the infrastructure around Konflux CI, the build system Red Hat has been developing: the clusters it runs on, the Terraform behind them, backups and disaster recovery, new releases, all the plumbing that has to keep working so that the teams building products on top of it never have to think about it.

Konflux itself is the part that probably needs introducing, because outside Red Hat it is still not widely known. It is an open source, Kubernetes native build system. Kubernetes native in a fairly literal sense: pipelines are custom resources living on a cluster, scheduled as Pods, with no separate build server standing next to them. What sets it apart from a general purpose pipeline runner is that it is built around supply chain security. Builds run with the network cut off, images are signed, an SBOM and SLSA provenance come out the other end, and a policy gate decides whether the result is allowed to be released at all. None of that is something you switch on. It is the default behaviour.

Here is the thing though. Running the infrastructure under a tool and actually using that tool are two different jobs. I knew Konflux well from the cluster side and much less well from the side of someone who just wants to ship a container image. So I decided to build something with it myself, start to finish, the way any user would, mostly to understand it properly.

In June 2026 I gave a presentation at DevConf.CZ in Brno called "From Podman to Production", based on exactly that. The idea was simple: take one Containerfile, build it twice, using podman on my local and then using Konflux CI, and look at what you can actually prove about each result.

A few people asked me afterwards to write it up, so here it is. The structure follows the talk pretty closely, screenshots and all.

The whole thing is built around one rule I kept repeating on stage: the Containerfile never changes. Not one line. What changes is everything around it.

One Containerfile, three acts: local Podman build, Konflux trusted pipeline, verify and run

The demo app

I wanted something that wasn't a hello world, so the demo app is a small Flask API that talks to the Pretalx instance behind DevConf.CZ. It pulls the real conference schedule: how many talks there are, the breakdown by session type, the room list.

It also has a /api/self endpoint that searches the schedule for the talk I was giving at that moment. So during the presentation the app was literally looking itself up in the conference system. That got a laugh, which is about all you can ask for from a demo.

Flask app on port 8080 pulling from the Pretalx API and exposing /api/stats, /api/rooms, /api/types and /api/self

Act 1: the build everybody already knows

Here is the Containerfile. Nothing clever about it.

FROM registry.fedoraproject.org/fedora:44

RUN dnf install -y python3-pip && dnf clean all
COPY app/ /opt/app/
RUN pip3 install -r /opt/app/requirements.txt

EXPOSE 8080
CMD ["python3", "/opt/app/main.py"]
podman build -t devconf-api:latest .
podman run -p 8080:8080 devconf-api:latest
curl -4 localhost:8080/api/stats

Three commands and you have a working API serving live conference data.

Terminal recording of podman build, podman run and curl against the local API

Podman is rootless, daemonless, OCI native. For the inner development loop this is as good as it gets. Fast feedback, no infrastructure, no waiting on a queue.

And for development that is genuinely fine. The problem starts when somebody asks you to ship it.

What you cannot prove

Picture a security review of that image. Can you prove which source commit produced it? No. Is it signed? No. Do you have an SBOM listing every dependency inside? No. Was the build isolated from the network? Definitely not, your laptop had full internet access the whole time. Did anything scan it? No. Was there a gate that could have stopped a bad build from being released? No.

Local build versus production ready: no provenance, no signature, no SBOM, open network, no scan, no policy gate on the left; SLSA provenance, cosign signature, SPDX SBOM, hermetic build, Clair and ClamAV, Enterprise Contract on the right

None of this means the image is bad. It means you have no evidence either way, and in a supply chain "trust me" is not evidence.

So the question for the rest of the talk was: can I keep this exact Containerfile and get all of that metadata without writing any of it myself?

Act 2: what Konflux actually is

Konflux is an open source, Kubernetes native trusted software factory. The important word in that sentence is trusted.

Konflux logo

The stack is all CNCF and upstream pieces:

Job Tool
Pipelines Tekton (Kubernetes CRDs)
Builds Buildah (the same engine Podman uses)
Signing Sigstore / cosign, keyless
Scanning Clair for CVEs, ClamAV for malware
Policy Conforma (Enterprise Contract)
Provenance in-toto, SLSA Level 3
Dependencies Hermeto for hermetic prefetch
Registry Quay, with OCI artifacts

Notice Buildah in there. That is the same build engine Podman shells out to, which is exactly why the Containerfile does not need to change. You are not learning a new build system, you are wrapping the one you already use in a lot of machinery.

The pieces you will see by name later are PipelineRun and TaskRun. Those are the Tekton custom resources a build turns into once it is running on the cluster, and you query them with oc get like anything else.

It runs on OpenShift, ROSA, EKS or plain Kubernetes, and it is Apache 2.0 across over 150 repositories on GitHub.

It is also not only a Red Hat thing. Fedora runs its own Konflux instance and has been moving real work onto it: Fedora CoreOS has an accepted Change for Fedora 44 to build the stable, testing and next streams there, and the reason given at the top of that page is getting SBOMs and attestations into the hands of end users. Same motivation as this article, at distribution scale.

What you need before you start

Three things:

  1. A Kubernetes cluster. I used OpenShift for the demo.
  2. A Konflux instance running on it.
  3. A Git integration. On GitHub that is a GitHub App, which is what enables Pipelines as Code. On GitLab you use a webhook plus an access token instead.

Konflux Staging GitHub App installation page

Onboarding is two YAML files

This is the part that surprises people. To put a repository under Konflux you apply two standard Kubernetes resources. Here they are in full, nothing trimmed for the article.

apiVersion: appstudio.redhat.com/v1alpha1
kind: Application
metadata:
  name: devconf-api
  namespace: vsok-tenant
spec:
  displayName: DevConf Schedule API

The Application is just a logical grouping, the umbrella that everything shipped together lives under. If I had a frontend and a backend they would be two Components under the same Application. Note the namespace, vsok-tenant. Konflux gives every tenant their own namespace and that is where all of this lives.

apiVersion: appstudio.redhat.com/v1alpha1
kind: Component
metadata:
  name: devconf-api
  namespace: vsok-tenant
spec:
  application: devconf-api
  componentName: devconf-api
  source:
    git:
      url: https://github.com/vsokolen-redhat/devconf-demo
      revision: main

Read the Component out loud and it is almost embarrassingly short: belongs to this Application, lives in this Git repo, build this branch. There is no build configuration in it at all. No base image, no build steps, no mention of the Containerfile, no registry credentials. Konflux works the rest out by itself.

oc apply -f application.yaml
oc apply -f component.yaml

The moment the Component lands on the cluster, several controllers wake up. The Build Service finds the Containerfile. The Image Controller provisions a Quay repository for the output. Pipelines as Code opens a pull request on my repo containing a .tekton/ directory.

I did not write that pipeline. Konflux wrote it for me and put it in my repository, next to the Containerfile, as code I can read and edit.

Pull request opened by the konflux-staging bot on the devconf-demo repo, titled "Pipelines as Code configuration proposal", all checks passed

Inside are two files: on-push.yaml, which builds when something merges to main, and on-pull-request.yaml, which builds every PR. Each one is a PipelineRun manifest with a CEL expression deciding when it fires.

I reviewed it, merged it, and that merge triggered the first real build.

The build

Konflux UI showing the DevConf Schedule API application activity, both pipeline runs succeeded

Here is the same thing from the terminal, which is where I spend most of my time anyway.

oc get pipelineruns and oc get taskruns showing init, clone-repository, prefetch-dependencies succeeded and build-container running

Worth walking through the tasks, because this is where the trust actually gets built.

init and clone-repository. Nothing exciting, a git clone.

prefetch-dependencies. This is Hermeto. It resolves and downloads every pip package the app needs, with network access, and generates a complete SBOM while it does it. Every dependency gets captured here and nowhere else.

build-container. The interesting one. Buildah runs with --network=none. The network is physically cut for the duration of the build. The only things available are what Hermeto already fetched. The analogy I used on stage was locking the chef in the kitchen with only the ingredients you approved, and no delivery door.

That is what a hermetic build means in practice, and it is also why the SBOM is trustworthy. Nothing can sneak in halfway through.

CVE scan and malware scan. Clair and ClamAV, automatically. No plugin to install, no config to write.

Signing and push. Tekton Chains watches every build in the cluster. When one completes it signs the image with cosign using keyless signing, generates SLSA Level 3 provenance, and attaches both to the image in Quay. Chains signs the image, the provenance attestation and the SBOM separately, so each one is independently verifiable.

All of that happened without me configuring a single thing beyond those two YAML files.

The gate

When the build finishes, Konflux creates a Snapshot: an immutable record of every component image at its exact SHA256 digest. Not a tag. A digest.

Konflux Snapshots tab listing two snapshots for devconf-api, with oc get snapshots in a terminal below

Then the Enterprise Contract runs. This is the policy gate, the bouncer at the door. It asks whether the image is signed, whether it has provenance, whether the build was hermetic, whether the CVE scan passed, whether the base image is still supported.

If any check fails, the release is blocked. There is no override flag, which is the entire point. A gate you can skip when you are in a hurry is not a gate.

Act 3: verifying it myself

Policy gates are nice, but I wanted to show the audience the evidence directly rather than ask them to take the UI's word for it. One command does that.

cosign tree quay.io/redhat-user-workloads-stage/vsok-tenant/devconf-api@sha256:ce0df74bba3fbfabc6469d931c774c0270f809edcf82bf658bee533cd35e0f77

cosign tree output listing attestations, three signatures, SBOM, Clair report, ClamAV report and two SARIF reports attached to the image

Everything hanging off that single image reference:

  • Three cosign signatures
  • An attestation holding SLSA Level 3 provenance
  • A full SBOM
  • A Clair CVE report
  • A ClamAV malware report
  • Two SARIF reports from static analysis

Six kinds of supply chain artifact, attached to one container image, none of which I asked for explicitly.

Running it

The last step of the demo was pulling the image back down and running it, the same way I ran the local build at the start. Not by tag, by digest, so I know it is exactly the image that was signed and attested.

podman pull by digest, podman run and curl returning the same DevConf stats from the trusted image

Same output. Same conference data. Same app.

That is the whole point, and it is worth being blunt about it: the difference is not what the app does. The difference is what you can prove about how it was built.

Go back to that list of questions from the start. Which source commit built this? The provenance says so. What is inside it? The SBOM says so. Was it isolated during the build? The provenance records the hermetic flag. Did anything scan it? Two reports say yes. Is it signed? Three signatures say yes. And none of those answers came from me writing pipeline code.

Five things to take away

Five takeaways: start with Podman, hermetic builds matter, signing is automatic, policy gates block bad releases, no custom YAML needed

  1. Start with Podman. Keep your inner loop fast. Your Containerfile moves to Konflux unchanged.
  2. Hermetic builds matter. Cutting the network during the build is what stops a dependency from changing under you, and it is what makes the SBOM complete rather than approximate.
  3. Signing is automatic. Tekton Chains plus cosign, keyless, on every build. No key management, no rotation, nothing to leak.
  4. Policy gates block bad releases. The Enterprise Contract validates provenance and scan results before anything can be released.
  5. No custom YAML needed. Konflux ships opinionated defaults. Point it at a repo, push code, get trusted images.

Or, the version I ended the talk with: your laptop builds containers, Konflux builds trust.

Try it yourself

Everything here is open source under Apache 2.0.

The slides from the talk are here.

Happy to talk about trusted pipelines, Tekton or Kubernetes native CI in general. You can find me on LinkedIn, or just leave a comment.

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.