Kubeflow Pipelines (KFP) and Open Data Hub (ODH) are the open source projects I've been contributing to the most in the last few years. KFP is a platform for building and deploying machine learning (ML) workflows on Kubernetes. ODH is an open source AI/ML platform that includes KFP as a core component, along with projects like Data Science Pipelines, its operator, and Argo Workflows. Open Data Hub is also the upstream project for Red Hat OpenShift AI.
In the first half of 2026, I spent a significant amount of time on work that doesn't usually make headlines: improving continuous integration and continuous delivery (CI/CD), simplifying builds, fixing flaky tests, and cleaning up code. None of it was a new feature. All of it made contributing to these projects easier.
Most of these improvements aren't specific to KFP or ODH. They're patterns you can apply to your own projects. This post walks through many of them, explains why they matter, and links to the pull requests (PRs) where I applied them across multiple repositories in both the Kubeflow and OpenDataHub GitHub organizations.
CI/CD: Faster, more reliable, less noisy
Every open source project with CI eventually faces the same problems: flaky tests, slow feedback loops, wasted resources, and cryptic failures unrelated to the contributor's change. When your PR fails for reasons you can't control, it's frustrating. When it happens repeatedly, people stop contributing.
Here are several patterns I applied that you can use in your own projects.
Extract duplicated CI logic into reusable composite actions
In kubeflow/pipelines, many workflows duplicated the Go setup logic. The motivation for changing this came from the ODH fork, where we needed to use Red Hat's Go distribution instead of the upstream one. Changing the Go setup in dozens of workflow files would have been painful, error-prone, and a constant source of merge conflicts between upstream and the fork. I refactored it into a reusable composite action, so the Go installation logic lives in 1 place.
We applied the same pattern in kubeflow/pipelines-components with a reusable Python CI setup action, so all Python workflows share the same installation and environment configuration.
Even if you don't have the same fork-specific reason, this pattern is worth adopting. If your GitHub Actions workflows repeat the same setup steps (installing a language runtime, configuring caching, setting environment variables), extract them into a composite action. It reduces maintenance burden and turns version bumps into a 1-line change instead of touching dozens of files.
Fix flaky tests at the root
Flaky tests erode trust in CI. Instead of adding retries masking the problem, look for the root cause. Common culprits include non-idempotent cleanup, race conditions, and immediate assertions on eventually consistent state.
Make test cleanup idempotent
The pipeline end-to-end (e2e) tests had a 2-step cleanup: first delete all pipeline versions, then delete the pipeline. If the first step partially ran or a previous test already deleted the pipeline, the second step would fail. I replaced this with cascade deletion, a single call removing the pipeline and all its versions, and removed the Get-before-Delete guard that broke on already-deleted resources. I also consolidated scattered inline cleanup code into reusable helpers with Ginkgo DeferCleanup for consistent teardown ordering.
Use Eventually instead of immediate assertions on eventually-consistent state
Kubernetes informer caches don't update instantly. Tests asserting "this list should be empty" right after a deletion will intermittently fail because the cache hasn't synced yet. I wrapped these assertions with Eventually, which retries until the assertion passes or reaches a timeout. This is a pattern you can apply anywhere you're asserting on state going through an informer or cache layer.
Don't trust no-op readiness checks
The e2e tests had a wait_for_seaweedfs_init() function checking for an init-seaweedfs Job to determine when S3 authentication was ready. But a previous PR had removed that Job, turning the function into a no-op. Tests would start before SeaweedFS had finished configuring S3 authentication, causing intermittent Signed request requires setting up SeaweedFS S3 authentication failures. I updated the readiness check to poll SeaweedFS and verify the kubeflow-admin identity is configured. The lesson: when you remove infrastructure a readiness check depends on, update or replace the check—don't leave a silent no-op.
Remove what isn't helping
Sometimes the best improvement is removing something. If a CI feature is causing more failures than it prevents, cut it.
I removed image caching from the CI build workflow because the cache was rarely hit and the caching logic itself was causing failures. I also added cancel-in-progress to all PR-triggered workflows, so pushing a new commit to a PR automatically cancels the previous run instead of wasting CI resources and making contributors wait.
Use what GitHub already gives you
GitHub Actions workflows receive rich event payloads. Before calling the REST API, check if the data you need is already in the webhook payload. It often is, and you avoid rate limits.
I replaced GitHub REST API calls with webhook payload data in the approval workflow. The API calls were hitting rate limits and causing workflow failures for contributors. The data we needed was already in the event payload all along.
Make test failures easier to diagnose
When an e2e test fails, contributors shouldn't have to hunt through separate log files. I embedded Argo Workflow logs directly into HTML test reports. Now the logs are right there in the report, and contributors can understand what happened without leaving the browser.
Federal Information Processing Standards (FIPS) simplification: From OpenSSL to native Go
If your Go project uses OpenSSL for FIPS 140 compliance, there's now a much simpler alternative. Go 1.24 introduced a native FIPS 140-3 cryptographic module, and Go 1.26 made it production-ready. Instead of linking against OpenSSL via C Go (CGO), with all the Dockerfile complexity, extra package installations, and cross-compilation headaches coming with it, you can configure FIPS compliance with a single directive in go.mod.
Here's what the migration looks like in practice: drop the OpenSSL packages from your Dockerfiles, remove the CGO requirement, and add the FIPS configuration to go.mod. The result is simpler Dockerfiles, faster builds, and as a direct consequence, much easier multi-architecture builds, especially on Apple Silicon.
I applied this migration across 3 ODH repositories:
- Switched Argo Workflows from OpenSSL FIPS to native Go FIPS 140-3
- Removed FIPS-related build logic from Data Science Pipelines Dockerfiles and Makefile
- Used native FIPS from Go 1.26.3 in Data Science Pipelines Operator (DSPO)
- Moved GODEBUG=fips140=on from Dockerfiles to go.mod in Data Science Pipelines (DSP)
Build anywhere: Apple Silicon and local dev
If contributors can't build your project locally, they're less likely to contribute. With more developers using Apple Silicon Macs, multi-architecture Dockerfile support is table stakes.
Here are a few techniques making a big difference across the KFP and ODH repositories.
Support cross-platform image builds
The first step was setting --platform=$BUILDPLATFORM in the DSPO Dockerfile builder stage so the build runs natively on the host architecture instead of emulating. This worked, but Go's assembler can segfault under Quick Emulator (QEMU) emulation, so we later evolved to a more robust approach: a BUILDER_ARCH multi-stage pattern with per-architecture digest-pinned base images. I applied this in DSPO with a new build-arm64.yml CI workflow to validate arm64 builds on an actual arm64 runner, and then applied the same pattern to all 5 backend images in DSP. I also fixed cross-architecture wget binary selection in the apiserver Dockerfile, where cross-compilation copied the wrong-architecture binary.
For Python, I added a platform-specific flag to requirements generation so make requirements produces correct Linux AMD64 dependencies even when run on macOS.
Provide non-CGO build paths for local dev
Not every local build needs CGO or FIPS. Give contributors a way to opt out when testing business logic. I added support for non-FIPS local builds on Apple Silicon in DSPO, added CGO_ENABLED override in the MLflow Operator, and added FIPS and architecture build options for Apple Silicon compatibility in DSP.
Remove unnecessary packages from Dockerfiles
Every package install in a Dockerfile adds build time and attack surface. I removed unnecessary package installation steps from back-end Dockerfiles in KFP, making builds faster for everyone.
Set secure defaults
New contributors shouldn't have to think about container security. I set a default non-root user in the DSPO Dockerfile, so container images are secure by default.
Code quality and consistency guardrails
Automate what you can, so code review focuses on logic instead of style and consistency. Small inconsistencies add up: when go.mod says Go 1.26 but a Dockerfile uses golang:1.25, someone eventually gets a confusing build failure. When utility functions are scattered across the code base, contributors reinvent them. When imports follow different conventions, code review becomes noisier than it needs to be.
Here are the guardrails and cleanups I added.
Automate version consistency checks
I added a CI workflow to enforce Go version consistency between go.mod and Dockerfiles in DSPO. If they drift, CI catches it before a human has to. You can adapt this pattern for any toolchain version appearing in multiple places in your repo.
Consolidate reusable scripts
I consolidated reusable utility functions into scripts/lib in kubeflow/pipelines-components, replacing scattered duplicates. When helpers live in 1 place, contributors find them instead of writing their own.
Enforce lockfile consistency
I added uv.lock sync checks in pre-commit and CI, so the lockfile always reflects dependency changes. The same idea applies to any package manager with a lockfile: go.sum, package-lock.json, poetry.lock.
Standardize conventions
I standardized test imports to use relative imports, making the test structure more consistent and easier to navigate. I also clarified Import Guard guidance and allowed top-level imports, reducing confusion about what import patterns we expect.
Document how AI agents should interact with your repo
As AI-assisted contributions become more common, it helps to have explicit guidance. I added an AGENTS.md file documenting how AI-assisted contributions should interact with the repo's CI, linters, and approval flow.
Wrapping up
None of these changes are individually groundbreaking. But they compound. Each removes a small friction point (a flaky test, a confusing build failure, a missing guardrail) and together they significantly lower the barrier to contribution.
If any of these patterns resonated, pick 1 or 2 and try them in your own projects. The PRs linked above are all public; feel free to use them as reference.
And if you're interested in contributing to any of these projects, check their repositories: