Building a CI/CD pipeline: the choices no vendor will tell you about
Most CI/CD guides explain what a pipeline is. This one covers the choices that actually bite: how paranoid to be about validation, keeping builds small, when to own your runners, push versus pull, and the one step worth keeping manual.

Search "ci/cd pipeline" and you get a hundred versions of the same article. Continuous integration, continuous delivery, a neat diagram with an infinity loop, a list of benefits. It's all technically correct. And none of it warns you about the decisions that come back to hurt you once you actually build one.
We build and audit these pipelines for a living. So this is the version you won't get from a tool vendor's landing page: the choices that actually matter, and how we make them.
Start with validation, and be almost paranoid about it
The whole point of a pipeline is a short feedback loop. A developer pushes, and within a minute or two they know whether something is wrong. Not after twenty minutes, and definitely not in production.
That only works if your first stage is ruthless. Linting for formatting and obvious mistakes. Static analysis for code quality and bad practices. Secret detection, because a password that lands in your git history stays there, even after you delete the line.
The instinct is to keep validation light so the pipeline feels fast. We'd argue the opposite. You can almost never have too much validation up front. Every check you run before the build is a check you're not paying for after a failed deploy, in real hours and real money. Fail at minute one. It's the cheapest failure you will ever get.
Keep your build small, or pay for it later
We see this at clients more than we'd like. Someone uploads a container and the deploy crawls. Why does it take so long? Because the container is a gigabyte. At that point you're not shipping a container, you're shipping a virtual machine with extra steps.
Multistage builds fix most of this. You compile in one stage, with all your dependencies, and copy only the finished artifact into a clean, small final image. A production container with no build tooling, just what it needs to run. An Nginx image drops from hundreds of megabytes back to tens.
The same discipline applies to the pipeline itself. Reach for a marketplace action for everything and you inherit its baggage. We've watched a single linter action pull an image so large that the step spent two minutes just preparing before it did any real work. Write that step yourself as a plain shell run and it finishes in thirty seconds. Smaller image, faster feedback, and you can read exactly what the step does.
Own your runners when it counts
By default your pipeline runs on GitHub-hosted runners. Nothing to configure, it just works, and for most teams that is genuinely enough.
Two things push you toward self-hosted runners. Cost, because hosted minutes are capped on private repositories and you don't want your pipelines stopping halfway through the month because a budget ran out. That one bites smaller teams hardest: on the bigger plans the included minutes are generous enough that most people never notice, but a growing scale-up on a starter plan runs into the ceiling exactly when its pipeline starts getting used properly. And access, because a hosted runner can't reach a workload behind a VPN or inside a private network without you exposing it. That one applies to everyone, whatever you're paying.
What most people don't realise is that a self-hosted runner doesn't need to be publicly reachable. It polls. Every minute or two it asks GitHub whether there is a job for it, over plain outbound HTTPS. Nothing needs to come in. So you can run a private environment, let the runner deploy straight into it, and never open a port to the outside. That property alone is worth the setup for a lot of the environments we work in.
Push or pull, decide it deliberately
There are two ways to get a build onto an environment, and teams tend to fall into one without choosing.
Push-based is the obvious one. Your pipeline initiates the deploy: put this container on Azure now. It works, and the tooling is usually specific to your cloud. For plenty of setups that is exactly the right call.
Pull-based flips the direction. Your Git repository becomes the single source of truth and the environment watches it. Change a tag in Git, and the cluster notices it's out of sync and pulls the new version itself. This is GitOps, with ArgoCD or FluxCD doing the reconciliation, and it's our preferred way to deploy once Kubernetes is in the picture. You get automatic rollback when a release goes wrong, and the same privacy benefit as the polling runner: the repository can be public, the environment stays private, and the deploy still happens.
Our short version: if you are running Kubernetes, use GitOps and ArgoCD. It's the tip we give most often.
Automate everything except the last step
Full automation is the goal right up until the moment code hits production. That last hop is where we almost always keep a human.
A deployment gets prepared automatically, tested, staged, and then someone physically approves the push to production. You lose "fully automated" as a bragging right. You gain control over the one step where a mistake is expensive.
A few guardrails we treat as non-negotiable. Make failing pipelines block the merge, not just report red. Keep production off your default branch, so nobody opens a pull request straight into it by accident. And keep secrets out of your code entirely, in a secrets store or a key vault, because git history has a long memory.
The tool is a detail, the discipline is the point
Notice how little of this was about a specific product. We have preferences. Trivy for container scanning, because it's open source and an industry standard. SonarQube's community edition for static analysis. Aikido, a Belgian option, is strong and worth supporting. But swap any of them for an equivalent and the pipeline is just as good.
That's the part a vendor article can't say, because its pipeline conveniently needs its product. Ours doesn't. The pattern is what carries: validate early, build small, control your runners, choose your deploy model on purpose, and guard the last step. The tool underneath is a detail you can change later.
If you're standing up a pipeline, or you've got one you're no longer sure about, that's a good conversation to have. We audit pipelines for exactly this, and it usually starts by just talking it through.

.jpg)

.jpg)