CI/CD Explained: Building a Pipeline That Ships Safely and Often
What CI/CD is and how to build a good pipeline — stages, fast feedback, testing strategy, build artifacts, environments, deployment strategies, secrets, rollbacks and DORA metrics.
Continuous integration and continuous delivery (CI/CD) turn shipping software from a stressful, occasional event into a routine, low-risk habit. Every change is automatically built, tested and prepared for release. The best teams deploy many times a day with fewer incidents than teams that deploy monthly, because small, frequent, automated changes are easier to verify and easier to roll back.
Key takeaways
- CI: every change is merged frequently and automatically built and tested.
- Continuous delivery: every change that passes the pipeline is deployable at any time. Continuous deployment goes further and deploys automatically.
- A good pipeline is fast (feedback in minutes), reliable (no flaky steps) and safe (progressive rollouts, easy rollback).
- Build once, deploy many: promote the same artifact through environments.
- Measure delivery performance with DORA metrics.
What a pipeline looks like
textCommit / PR
│
├─▶ CI: lint → type check → unit tests → build → integration tests → security scans
│ (all in parallel where possible; fail fast)
│
├─▶ Artifact: versioned container image / bundle stored in a registry
│
└─▶ CD: deploy to staging → smoke tests → deploy to production (progressively)
→ post-deploy verification → automatic rollback on failure
Continuous integration: fast, trustworthy feedback
Principles
- Merge small changes frequently. Long-lived branches create painful merges and hidden integration bugs.
- Every PR runs the pipeline, and failures block merging.
- Keep CI fast: aim for about 10 minutes or less for the main feedback loop. Slow CI encourages batching changes and skipping steps.
- Fix broken main immediately. A red main branch blocks everyone.
Typical CI stages
- Install dependencies (with caching).
- Static checks: formatting, linting, type checking (TypeScript best practices).
- Unit tests, then integration tests against real services in containers (testing strategy guide).
- Build the deployable artifact.
- Security checks: dependency vulnerability scanning, secret scanning, static analysis, container image scanning.
Example: GitHub Actions workflow
yamlname: ci
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env: { POSTGRES_PASSWORD: test }
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready --health-interval 5s --health-retries 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run lint && npm run typecheck
- run: npm test -- --coverage
env: { DATABASE_URL: postgres://postgres:test@localhost:5432/postgres }
- run: npm run build
Speeding up CI
- Cache dependencies and build outputs.
- Parallelize test suites across jobs.
- Run only affected tests or packages in monorepos.
- Use faster runners for heavy jobs.
- Quarantine flaky tests immediately (with an owner and a deadline) rather than retrying them silently.
Build once, deploy many
Build an immutable, versioned artifact (such as a container image tagged with the commit SHA) once, then promote that exact artifact through staging and production. Rebuilding per environment risks "it worked in staging" surprises (Docker explained).
Configuration differs per environment through environment variables and secrets, not different builds.
Continuous delivery and deployment
- Continuous delivery: every change on main is deployable; a human clicks "deploy" (or a release happens on a schedule).
- Continuous deployment: every change that passes all checks goes to production automatically.
Either way, deployments should be automated, repeatable and boring.
Environments
- Preview environments per pull request are great for review, QA and product feedback.
- Staging mirrors production as closely as practical: same infrastructure code, similar data shape.
- Production, deployed progressively.
Define all environments with infrastructure as code so they don't drift (infrastructure as code with Terraform).
Safe deployment strategies
- Rolling: replace instances gradually.
- Blue-green: switch traffic between two identical environments.
- Canary: send a small percentage of traffic to the new version and watch metrics before expanding.
- Feature flags: deploy code dark and release features independently of deploys.
Details in zero-downtime deployments.
Database migrations
Migrations are the riskiest part of many deploys. Use backward-compatible migrations (expand → migrate → contract) so old and new code can run simultaneously during rollout, and run migrations as a separate, observable pipeline step.
Secrets and security in pipelines
- Store secrets in the CI platform's secret manager or a dedicated vault. Never in the repository.
- Prefer short-lived credentials (for example, OIDC federation from CI to your cloud provider) over long-lived keys.
- Apply least privilege: the pipeline that deploys the frontend shouldn't be able to delete databases.
- Pin third-party actions and dependencies to specific versions or commit SHAs to reduce supply-chain risk.
- Protect the main branch: required reviews and required status checks.
Post-deploy verification and rollback
- Smoke tests hit critical endpoints right after deploy.
- Automated canary analysis compares error rates and latency between old and new versions (observability explained).
- One-click (or automatic) rollback to the previous artifact.
- Make rollback routine. If it's scary, it won't be used when needed.
Measuring delivery performance: DORA metrics
| Metric | What it measures |
|---|---|
| Deployment frequency | How often you deploy to production |
| Lead time for changes | Commit → running in production |
| Change failure rate | % of deployments causing incidents or rollbacks |
| Time to restore service | How quickly you recover from failures |
Research by the DORA program has found that high performers do well on both speed and stability. Small batches, automation and fast recovery enable both. Track these metrics to see where your pipeline and process need work.
CI/CD for data and ML
The same principles apply beyond application code:
- Data pipelines: DAG integrity tests, SQL model tests and staged deploys (building data pipelines with Airflow, dbt explained).
- ML models: automated training, evaluation gates and staged rollout (MLOps explained).
- LLM features: eval suites run on prompt and model changes (how to evaluate LLM applications).
A maturity path
- Automated build and tests on every PR.
- Automated deploy to staging on merge.
- One-click production deploys of the same artifact, with rollback.
- Progressive delivery (canary / blue-green) with automated verification.
- Continuous deployment with feature flags, plus DORA metrics tracked and improving.
FAQ
What's the difference between continuous delivery and continuous deployment?
Continuous delivery keeps every change in a deployable state, with production releases triggered by a person or schedule. Continuous deployment automatically releases every change that passes the pipeline to production.
Which CI/CD tool should I use?
Popular options include GitHub Actions, GitLab CI/CD, CircleCI, Jenkins, and cloud-native services. Choose based on where your code lives, the integrations you need, security requirements and team familiarity. The practices matter more than the tool.
How fast should a CI pipeline be?
Fast enough that developers wait for results instead of context-switching away. Roughly 10 minutes or less for the main feedback loop is a common goal. Longer suites can run in parallel or later stages.
Do small teams need CI/CD?
Yes. Even a simple pipeline (tests on every PR, automated deploys) saves time, prevents mistakes and makes deploys routine from day one. It's one of the highest-leverage investments a small team can make.