There's a small detail in many deployment pipelines that decides whether the system is clean or painful: who turns the code into the thing Kubernetes actually runs?
In the Oscar Mobility work, that detail led me to Pants.
The project has a Python services repository. It also has a GitOps repository with Kubernetes manifests. That split is normal. The code lives in one place. The cluster state lives in another. The question is what happens between them.
If that middle step is loose, GitOps becomes theatre. You can have Flux, Kustomize, clean YAML, and a nice folder structure, but if the image tag in Git points to something built by a different script, with different dependencies, or from a different commit, the chain is already broken.
Pants helps close that gap.
What Pants is doing here
Pants lives in the services repository. It is not the deploy tool. It does not apply Kubernetes manifests. It does not replace Flux.
Its job is earlier in the chain.
Pants knows the Python packages, tests, type checks, PEX binaries, and Docker image targets. In this project it is configured for Python 3.13, Ruff, Mypy, tests, PEX packaging, and Docker image publishing.
That means CI can ask precise questions:
- Which service changed?
- Which tests belong to it?
- Which PEX binaries exist for it?
- Which Docker images should be built?
Then CI can run commands like:
pants lint packages/<service>:
pants check packages/<service>:
pants test packages/<service>:
pants package <pex-targets> <image-targets>
pants publish <image-targets>
The useful part is not the command syntax. The useful part is that the build graph is explicit.
The deployable image is not a side effect of a random docker build step. It is a declared target in the repo.
Why PEX matters
A PEX file is a Python executable package. Pants builds the service into a PEX, then puts that PEX into a Docker image.
That gives the team a cleaner artifact boundary:
- Python dependencies are resolved before the image is built.
- The same PEX can be uploaded as a CI artifact.
- The Docker image becomes small and predictable.
- The container does not need to rebuild Python dependencies at runtime.
For a service like credit-check, this matters because it does more than expose HTTP. It also has migrations and provider adapters. The previous design used a single image carrying the API runtime and Alembic migration tooling. The initContainer runs the migration from the same artifact that later serves traffic.
That avoids a nasty class of bugs: the migration image and the API image drifting apart.
Where GitOps starts
GitOps starts after the image exists.
In this setup, the service CI publishes an OCI image tagged with the commit SHA. Then the deploy flow updates the GitOps repo.
The GitOps repo, infra-config, has Kustomize files. A deployment does not say "run whatever image is latest." It says:
images:
- name: service-image
newName: registry.example.com/team/service
newTag: <commit-sha>
Flux watches that repo. When the tag changes in Git, Flux reconciles the cluster.
So the chain becomes:
commit -> Pants target -> PEX -> OCI image -> Kustomize newTag -> Flux -> cluster
That's the part I like. Each step leaves a receipt.
You can point to the commit. You can point to the image tag. You can point to the Kustomize change. You can point to Flux applying it.
Why this is useful
The value of Pants here is not that it's fashionable. It's useful because it makes the build side boring.
In a growing Python monorepo, boring is good.
Without a build graph, every new service tends to copy a slightly different CI script. One service uses one Dockerfile pattern. Another builds a wheel. Another uses uv directly. Another forgets to include a runtime file. After a few services, CI becomes folklore.
Pants gives the repo a single way to describe deployable things.
For GitOps, that matters because Flux can only be as trustworthy as the artifact it deploys. GitOps says what should run. Pants helps prove what was built.
The current rough edge
There's still a rough edge in this project.
The newer services repo flow is moving toward Pants and GHCR. The existing infra-config deployment flow still has parts aimed at pricing and search, with images coming from GitLab Registry.
That's normal during adoption. A platform rarely moves all at once.
The important thing is to see the direction:
- Services should declare their PEX and Docker targets in Pants.
- CI should publish images tagged by commit SHA.
infra-configshould update the Kustomize image tag.- Flux should reconcile from Git.
Once those pieces line up per service, deployments stop depending on memory. The repo itself describes the path from code to cluster.
The practical lesson
Pants is not the whole delivery system.
It's the piece that makes the artifact trustworthy before GitOps takes over.
That's the lesson I'm taking from this project. GitOps is clean when Git holds the desired cluster state. It becomes much cleaner when the image referenced in Git was produced by a build graph, not by a pile of scripts nobody wants to debug on a Friday.
For the next microservice, especially something like credit-check, I'd rather define the build once in Pants, publish the image by SHA, and let Flux deploy exactly what Git says.
That gives the team a simple question after every release:
What commit is running?
And a simple answer:
The one in the image tag, the one in Kustomize, the one Flux applied.