A CI/CD pipeline should make shipping faster. But badly designed pipelines become the very bottleneck they were meant to eliminate. Here are the anti-patterns I see most often.
1. The Monolithic Pipeline
The problem: One massive pipeline that builds, tests, lints, scans, deploys, and makes coffee. If any step fails, you start from scratch.
| |
The fix: Parallelize independent stages. Lint doesn’t need to wait for build. Security scanning can run alongside tests.
| |
2. Testing Everything Every Time
The problem: Your pipeline runs the entire test suite on every commit, even when you only changed a README.
The fix: Path-based triggers and test selection.
| |
3. The “Works on My Machine” Build
The problem: Builds depend on cached state or undeclared dependencies. They pass sometimes, fail mysteriously other times.
The fix: Hermetic builds. Everything the build needs should be explicitly declared.
| |
Use lockfiles. Pin base images to digests, not tags. Treat “latest” as a bug.
4. Secrets Sprawl
The problem: Every pipeline has its own copy of production credentials. Nobody knows which ones are still in use.
| |
The fix: Centralize secrets. Use OIDC federation where possible — no static credentials at all.
| |
5. No Local Reproducibility
The problem: The only way to test the pipeline is to push a commit. Developers make 47 “fix CI” commits to debug a YAML typo.
The fix: Make pipelines runnable locally.
| |
Your pipeline should call scripts, not contain logic. The pipeline is orchestration; the scripts are the work.
6. The Approval Gauntlet
The problem: Every deploy requires three manual approvals, a change ticket, and a blood sacrifice.
| |
The fix: Automate the gates. If security scan passes, that’s the security approval. If tests pass, that’s QA approval. Reserve manual gates for genuinely risky changes.
| |
7. Ignoring Pipeline Performance
The problem: Nobody tracks how long pipelines take. They slowly grow from 5 minutes to 45 minutes, and everyone just accepts it.
The fix: Treat pipeline time as a metric. Set budgets. Alert on regression.
| |
Plot pipeline duration over time. When it creeps up, investigate.
8. Flaky Tests That Everyone Ignores
The problem: Tests fail randomly 10% of the time. The team’s solution is to just re-run the pipeline.
The fix: Quarantine flaky tests. Track them. Fix them. Don’t let them pollute your signal.
| |
A flaky test is worse than no test — it trains people to ignore failures.
9. No Caching Strategy
The problem: Every build downloads the entire internet. NPM, pip, Maven, Docker layers — all fresh, every time.
The fix: Cache aggressively. Use content-addressed caches where possible.
| |
For Docker, use BuildKit cache mounts:
| |
10. Deploy First, Ask Questions Later
The problem: The pipeline deploys directly to production with no verification. You find out it’s broken when customers tell you.
The fix: Progressive delivery. Deploy to canary first. Run smoke tests. Watch metrics. Then proceed.
| |
The Meta-Lesson
Every anti-pattern here shares a root cause: treating the pipeline as an afterthought instead of a first-class product.
Your pipeline is infrastructure. It needs monitoring, maintenance, documentation, and ownership. It deserves the same engineering rigor as your application code.
If your pipeline is slow, flaky, or confusing — that’s a bug. Prioritize it like one.