Shipping software should be boring. Not the building part — the shipping part. When a merge to main quietly becomes a running production release without anyone SSHing into a box at 11pm, you have a CI/CD pipeline doing its job. This is a practical walk through what CI/CD actually is, the stages a real pipeline runs, and a look at the exact pipeline behind this site.
What is a CI/CD pipeline, really?
A CI/CD pipeline is an automated path that takes a code change from a commit to a deployed release without manual steps in between. It splits into two halves:
- CI (Continuous Integration) — every change is merged, built, and tested automatically, so integration problems surface in minutes instead of at release time. Martin Fowler's canonical write-up on Continuous Integration is still the clearest definition of the practice.
- CD (Continuous Delivery/Deployment) — a change that passes CI is automatically packaged and rolled out. Delivery stops at a deployable artifact awaiting a click; deployment goes all the way to production on its own (Fowler draws the same delivery-vs-deployment line).
The payoff is small, frequent, low-risk releases instead of big, scary ones. Google's long-running DORA research consistently finds that teams deploying more often and recovering faster outperform teams that batch changes into rare, large releases — speed and stability move together, not against each other.
The stages of a typical pipeline
Most pipelines are a sequence of gates. A change only moves forward if the stage before it passed:
- Source — a push or pull request triggers the run.
- Lint & static analysis — style and security checks (for this repo: RuboCop and Brakeman).
- Test — the automated suite runs against a clean environment.
- Build — the app is compiled into an immutable artifact, usually a container image.
- Publish — the artifact is pushed to a registry, tagged by version.
- Deploy — the new artifact is rolled out to an environment.
A change only advances when the previous gate passes.
A real pipeline: how this site ships
Theory is cheap, so here is the actual flow behind this API. It runs on GitHub Actions and splits work by trigger:
- On every pull request — lint, test, and a build (the image is built but not pushed). This proves the change is releasable before it ever merges.
-
On every push to
main— lint and test run again on the merged result. -
On a version tag (
v1.2.3) — the release build kicks in: it builds alinux/amd64image, tags it, and pushes it to Amazon ECR.
Cutting a release is deliberately a single, boring command:
git tag v1.0.0 && git push origin v1.0.0
From there, Argo CD takes over. It watches a separate Kubernetes manifests repo, notices the new image tag, and reconciles the cluster to match — a pull-based model called GitOps, where the desired state lives in Git and the cluster continuously converges on it. Nobody runs kubectl apply by hand.
main run lint + test only; the build-and-push path fires solely on a version tag.Why split the pipeline by trigger?
Building on PRs catches breakage early and cheaply. Pushing images only on tags keeps the registry clean and makes releases intentional: a merge is not a release, a tag is. That separation is what lets the whole thing stay boring.
What a good pipeline actually gives you
- Fast feedback — you find out a change is broken in minutes, in a PR, not in production.
- Repeatability — the same steps run the same way every time; no "works on my machine".
- Immutable artifacts — the exact image tested is the exact image deployed.
- An audit trail — every release maps to a tag, a commit, and a green pipeline run.
Getting started without over-building it
You do not need Kubernetes and GitOps on day one. Start with the smallest pipeline that removes manual work:
- Run your tests automatically on every pull request. That alone is most of the value of CI.
- Add lint and a security scan as separate, non-blocking-then-blocking gates.
- Build a container image so your artifact is reproducible.
- Automate the deploy last — once you trust the gates above it.
Add complexity only when a real pain point demands it. A pipeline that runs tests on every PR and deploys on a tag will carry a project a very long way before it needs anything fancier.
The bottom line
CI/CD is not a tool you buy — it is the discipline of automating the path from commit to production so that shipping stops being an event. Get the gates right, keep artifacts immutable, and let a version tag do the work an on-call engineer used to do at midnight.
Need infrastructure work like this done right? See what I do or look at recent builds.