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,
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.
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.
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.
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.
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.
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:
- A Kubernetes cluster. I used OpenShift for the demo.
- A Konflux instance running on it.
- 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.
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.
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
Here is the same thing from the terminal, which is where I spend most of my time anyway.
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.
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
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.
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
- Start with Podman. Keep your inner loop fast. Your Containerfile moves to Konflux unchanged.
- 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.
- Signing is automatic. Tekton Chains plus cosign, keyless, on every build. No key management, no rotation, nothing to leak.
- Policy gates block bad releases. The Enterprise Contract validates provenance and scan results before anything can be released.
- 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.
- My presentation on DevConf.cz: youtube
- Konflux: konflux-ci.dev
- Quick start: konflux-ci.dev/docs/getting-started/
- Source: github.com/konflux-ci
- OpenShift Local (CRC), if you want a cluster on your laptop: developers.redhat.com/products/openshift-local
- Podman: podman.io
- Tekton: tekton.dev
- Sigstore: sigstore.dev
- SLSA: slsa.dev
- Buildah: buildah.io
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.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.











