At a securities brokerage in Indonesia we moved about 100 applications onto standardised GitLab CI and ArgoCD pipelines as part of a Kubernetes platform migration. If you’d asked me at the start where the effort would go, I’d have said pipeline templates, runner setup, ArgoCD app-of-apps, maybe some Helm wrangling.
That was maybe a fifth of it.
The rest was getting a hundred teams, with a hundred slightly different ways of shipping, to accept one way. And to accept the bits they didn’t like: image governance, deployment controls, approvals, freezes. The pipeline is easy. The pipeline as a policy enforcement point is the job.
Why “one pipeline” is a governance decision
The moment you say “every production deploy goes through this”, you’ve made promises to three groups at once.
To security: only images from the approved registry, built by a runner we control, scanned, signed, with the base image we said. To operations: nothing reaches prod without dual approval, nothing deploys during a freeze, everything is auditable after the fact. To the app teams: your build stays yours, the template won’t break you on Tuesday, and there’s a real exception path when the standard genuinely doesn’t fit.
Break any one of those and the standard erodes. Security stops trusting it, ops stops relying on it, or teams start routing around it. Usually all three, in that order.
What we actually enforced
- Image governance. Registry allow-list, pinned base images, scanning in the pipeline. An image that didn’t come from our build path couldn’t be deployed, full stop.
- Deployment controls. Environment protection so a staging job can’t target prod. Dual approval on production. Deploy freezes enforced in GitLab, not in a Slack announcement.
- Runner security. Hardened runners, scoped tokens, no long-lived credentials in job variables. This came out of a security audit remediation programme and it’s the piece most teams skip until an auditor makes them.
- Terraform guardrails. Policy checks in the IaC path so the platform itself was held to the same standard as the apps riding on it.
Then observability across all of it (Prometheus, Grafana, Wazuh for the security side), so “the standard is working” was a dashboard rather than an assertion.
Sequencing is the whole game
You can’t onboard 100 apps at once and you shouldn’t onboard them alphabetically. We sequenced by risk and by coupling: low-risk, well-understood services first to shake out the template, then the tightly coupled clusters of services together so a half-migrated dependency chain never sat in production, then the sensitive workloads last with the most rehearsed rollback.
Each wave shifted real traffic in stages up to 100%, with a tested rollback at each step. Zero unplanned downtime across the programme. The reason it was dull is that the governance was decided before the first pipeline ran.
The exception path is part of the standard
The fastest way to kill a platform standard is to have no legitimate way around it. Someone will have a genuinely weird build. If the answer is “no”, they’ll fork the template and you’ll find out in the audit.
We had an exceptions process: documented, time-boxed, reviewed. Most exceptions became template features within a couple of sprints, which is exactly what you want. The standard got better because people could push on it.
The handover test
The measure of whether it was governance or just enforcement: could the client’s own team run and extend it after we left? We handed over SOPs, runbooks, and knowledge transfer sessions, and they’ve kept extending the platform on their own, with me on an advisory retainer rather than on the critical path.
If a hundred teams are on your pipeline and you’re the only one who understands the rules, you don’t have a platform. You have a bottleneck with YAML.