backend / cicd / 01_cicd_basics.md

CI/CD Basics

7 interview angles 8 min read source

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.