CI/CD Basics
CI (Continuous Integration), CD (Continuous Delivery or Deployment). The pipeline that takes a code commit and turns it into running production software — automatically. Interview questions test whether you know the vocabulary, the phases, and the trade-offs.
The three terms
| Term | What |
|---|---|
| Continuous Integration (CI) | every commit is automatically built and tested |
| Continuous Delivery (CD) | every passing commit is automatically prepared for release; deploy is a manual button |
| Continuous Deployment (CD) | every passing commit is automatically deployed to production |
The two CDs are the trap. “Continuous Delivery” = ready to deploy, awaiting approval. “Continuous Deployment” = no human in the loop after merge.
Most teams say “CD” loosely. The distinction matters when an interviewer asks “do you deploy continuously?” — be specific about whether there’s a human gate.
The typical pipeline phases
Commit → Build → Unit Test → Static Analysis → Integration Test → Package → Deploy (staging) → E2E Test → Deploy (prod)
Roughly mapped:
| Phase | What |
|---|---|
| Source | the trigger — a commit / PR / merge to main |
| Build | compile, transpile, generate artifacts |
| Test | unit tests; ideally fast (seconds to a few minutes) |
| Static analysis | linter, type checker, security scan (SAST), license check |
| Package | build container image / installer / archive |
| Integration test | spin up dependencies (DB, cache), test against them |
| Deploy (lower env) | staging / pre-prod |
| E2E / smoke test | hit the deployed system through its real interfaces |
| Deploy (prod) | rolling / blue-green / canary; see 05_deployment_strategies.md |
| Verify | monitor metrics post-deploy; rollback if SLOs break |
Some phases run in parallel (unit tests + lint + security scan). Some are conditional (only on main, only on tags).
CI vs CD — separated
Sometimes the line is:
CI: build + test + package
───────────────────────────
CD: deploy + verify
Many teams have “CI on every PR; CD on merge to main.” The PR pipeline tests but doesn’t deploy; the merge pipeline deploys. Separating them lets you run CI cheaply on every push without consuming production access.
Trunk-based development + CI/CD
Modern high-performing teams ship from one branch (main / trunk):
- Short-lived feature branches (≤ 1 day).
- Merge to main triggers full pipeline.
- Behind feature flags for incomplete work.
- Multiple deploys to production per day.
See ../22_git/10_branch_strategies.md. Trunk-based pairs naturally with CD; long-lived branches break CD because integration only happens at merge.
DORA metrics — the standard performance measures
From “Accelerate” research (Forsgren, Humble, Kim):
| Metric | What |
|---|---|
| Deployment Frequency | how often you ship to production |
| Lead Time for Changes | commit → production time |
| Mean Time to Recover (MTTR) | how fast you recover from incidents |
| Change Failure Rate | % of deploys causing user-visible incidents |
The thesis: high-performing teams excel at all four. They correlate strongly with business outcomes. Used as the standard yardstick for “is our delivery healthy?”
Elite vs low performance (from DORA’s annual State of DevOps):
| Elite | Low | |
|---|---|---|
| Deploy frequency | multiple per day | every 1-6 months |
| Lead time | < 1 hour | 1-6 months |
| MTTR | < 1 hour | 1-6 months |
| Change failure rate | 0-15% | 16-30% |
The pattern: elite teams ship fast AND fail less. They’re not trading speed for stability.
What makes a good pipeline
| Quality | Why |
|---|---|
| Fast | feedback in minutes, not hours. Slow pipelines = batched commits, less testing |
| Deterministic | same input → same result. Flaky tests destroy trust |
| Self-service | devs can re-run, see logs, debug without a DevOps gatekeeper |
| Observable | logs, metrics, durations per step — find slow phases |
| Resilient | retry transient failures; clear errors |
| Secure | secrets handled properly; no logs leaking credentials |
| Versioned | pipeline-as-code; reviewable in PRs |
Common red flags: 1-hour test suites, “rerun job until it passes,” CI configured in a UI rather than in code, secrets in plaintext logs.
Pipeline-as-code
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.14" }
- run: pip install -r requirements.txt
- run: pytest
The pipeline definition lives in the repo. Reviewable in PRs. Version-controlled with the code. Branching the code branches the pipeline.
Pre-pipeline-as-code era: Jenkins jobs configured through a web UI. State lives on the Jenkins master; lost if the server dies. Hard to review. Hard to roll back. Avoid.
Pull vs push triggers
| Trigger | When |
|---|---|
| Push to branch | runs on every push |
| Pull request open / sync | runs on PR creation and updates |
| Tag pushed | release pipelines |
| Schedule (cron) | nightly tests, security scans |
| Manual / workflow_dispatch | on-demand runs |
| External webhook | when another system changes |
| Repository_dispatch | inter-repo triggering |
For CI: typically pull_request + push to main. For CD: push to main or tag.
Caching — the speed lever
Most pipelines spend time downloading dependencies. Cache them:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ hashFiles('requirements.txt') }}
When requirements.txt is unchanged, restore from cache (seconds) instead of pip install from network (minutes).
Cache common things:
- Dependency manager caches (
~/.cache/pip,node_modules,~/.m2, etc.). - Build outputs (compiled assets, Docker layer cache).
- Test data fixtures (avoid re-downloading on every run).
Bad caching is sometimes worse than no caching: stale cache hits make build failures mysterious. Cache keys must reflect what they cache.
Test layering for fast feedback
Unit tests ← seconds; run on every commit
Integration tests ← minutes; run on PR
E2E tests ← minutes-tens; run on merge to main
Load tests ← hours; run nightly
Fast tests run early; slow tests run later. The pipeline can fail fast on cheap checks before spending time on expensive ones.
Tooling like pytest with --fail-fast, GitHub Actions job dependencies, and concurrency policies let you stop on the first failure.
Common pipeline antipatterns
Flaky tests
A test fails 1% of the time for reasons unrelated to code. Devs learn to rerun until green. Trust in CI dies. The cure is to fix or quarantine — not normalize the rerun.
“Just deploy from my laptop”
Pipeline is broken; someone has to ship. They run the build locally and copy artifacts up. The deploy works but auditability is gone, and the broken pipeline stays broken longer.
Secrets in code or logs
echo $DB_PASSWORD in a script. Repository now contains a permanent record of the password. Use the CI platform’s secret store; never echo secrets. See 06_secrets_and_supply_chain.md.
Linear, slow pipelines
5 steps, each takes 5 minutes, all sequential = 25-minute pipeline. Parallelize independent steps. Most pipelines waste real time on coordination.
Snowflake build environments
“It builds on Bob’s laptop.” Pipeline configuration drifts from local; CI passes but local doesn’t, or vice versa. Use the same containerized environment for both.
No artifact reproducibility
Build twice from the same commit → different artifacts (different timestamps, different file order). Hard to audit; reproducible builds matter for security supply chain.
Pipelines that gate without authority
CI fails on a flaky lint rule no one cares about. Devs add # noqa or merge anyway. Either fix the rule, remove it, or make it informative-not-blocking.
CI/CD platform landscape
| Platform | Notes |
|---|---|
| GitHub Actions | Workflows in repo. Cloud-hosted runners + self-hosted. The default for GitHub repos. |
| GitLab CI | First-class CI integrated with GitLab. Excellent. |
| CircleCI | Cloud-first, fast. Lost ground to GitHub Actions. |
| Jenkins | Veteran. Powerful but operationally heavy. Mostly legacy. |
| Buildkite | Hybrid (cloud control plane + your runners). Strong at scale. |
| Bitbucket Pipelines | Atlassian; tight Bitbucket integration. |
| Azure DevOps | Microsoft; popular in .NET shops. |
| Drone, Tekton, Argo Workflows | Kubernetes-native CI. |
| TeamCity | JetBrains; deep IDE integration. |
For new projects on GitHub: GitHub Actions is the default. For GitLab orgs: GitLab CI. For polyglot or Kubernetes-heavy: Argo / Tekton. Avoid greenfield Jenkins unless you have specific reasons.
Interview angle
- “Difference between CI and CD?” — CI is automatic build + test on every commit. CD has two flavors: Continuous Delivery (ready to deploy, manual approval) vs Continuous Deployment (no human in the loop after merge). Most teams say “CD” loosely; be specific in interviews.
- “Walk me through a typical pipeline.” — trigger (push/PR) → checkout → install deps → lint + unit tests in parallel → build artifact → integration tests → deploy to staging → smoke tests → deploy to prod → monitor. Stages run in parallel where independent.
- “What are DORA metrics?” — Deployment Frequency, Lead Time for Changes, MTTR, Change Failure Rate. From “Accelerate.” Elite teams ship many times per day, recover in <1 hour, fail <15% of deploys.
- “What makes a pipeline ‘fast’?” — parallelize independent steps, cache dependencies, run tiers (cheap tests first, expensive last), don’t reinstall toolchains, use Docker layer cache.
- “How would you handle flaky tests in CI?” — fix or quarantine, never normalize retries. Track flakiness rate. Use deterministic test data; mock external services; isolate test state.
- “What’s pipeline-as-code and why?” — pipeline config in the repo (YAML), reviewed in PRs, branchable with code. Replaces UI-configured Jenkins-style jobs. State lives in code, not on a CI server.
- “How do you handle secrets in CI?” — the platform’s secret store (GitHub Secrets, GitLab Variables, Vault). Inject as env vars at runtime; never echo; rotate regularly. For cloud auth, prefer OIDC over long-lived keys. See 06_secrets_and_supply_chain.md.