DevOps Deep Dive

CI/CD — From Commit to Production

Continuous Integration merges everyone's changes into one branch, frequently, with tests proving they didn't break anything. Continuous Delivery takes the result and ships it. Done well, the path from a developer's keystroke to running production code is automated, observable, and unremarkable.

CICDPipelinesTrunk-basedArtifactsDeploys
← Back to DevOps
Quick Facts

Three Letters That Get Confused

The vocabulary

  • CI — Continuous Integration: every commit is merged to mainline frequently, with automated build + test on each merge.
  • CD — Continuous Delivery: every passing build produces a release artifact you could deploy with the push of a button.
  • CD — Continuous Deployment: every passing build is deployed automatically. Same acronym, one extra step.
  • Pipeline: the ordered sequence of stages a change goes through (lint → test → build → deploy).
  • Artifact: the immutable thing the build produces — a JAR, a container image, a static bundle.
Why

What CI/CD Is Actually For

  • Smaller batches. Ten 50-line changes are easier to review and revert than one 500-line change.
  • Fast feedback. A test failure on your branch beats a test failure in QA two weeks later.
  • Repeatable releases. The 47th deploy is as boring as the first. Boring is the goal.
  • Auditability. Every artifact in production traces back to a commit, a build, a set of tests.
  • Lower risk. Counterintuitive: deploying more often is safer because each deploy is smaller and the rollback is rehearsed.

DORA's research is clear: elite teams deploy multiple times per day with lower change-failure rate than teams who deploy quarterly.

Branching

The Branching Model Behind CI/CD

ModelIdeaWhen it fits
Trunk-basedShort-lived branches (< 1 day), merge to main behind feature flags.The default for CI/CD-driven teams. Pairs naturally with CD.
GitHub Flowmain + short feature branches; deploy from main.Small/mid teams. Effectively trunk-based with PRs.
GitFlowmain, develop, release branches, hotfix branches.Long-release-cycle products. Heavyweight; usually overkill.
Release TrainsCut a release branch on a fixed schedule; backport fixes.Mobile apps, OS releases, anything you can't auto-update.

CI/CD shines under trunk-based. Long-lived branches turn merges into archaeology and break the "one mainline, always green" property CI depends on.

Pipeline Anatomy

What a Real Pipeline Looks Like

  1. Source trigger — push to a branch or open a PR.
  2. Lint & format check — fail fast on style violations (~seconds).
  3. Type check — TypeScript, mypy, etc.
  4. Unit tests — most of the suite, in parallel where possible.
  5. Build — produce the artifact (container image, binary, bundle).
  6. Integration tests — against ephemeral services / containers.
  7. Security scans — SAST, secret scan, dependency scan, container image scan.
  8. Publish artifact — push to registry / artifact store with an immutable tag.
  9. Deploy to preview / staging — for PRs, a unique URL per PR is gold.
  10. E2E / smoke tests — against the deployed environment.
  11. Promote to production — automatically (CD) or with a human approval gate.
  12. Post-deploy verification — synthetic checks, SLO burn-rate watch, automatic rollback on failure.

Order matters: cheap, fast checks first. A 30-second lint failure beats waiting 12 minutes for the build to fail on a typo.

Tools

Where Pipelines Live

ToolSweet spot
GitHub Actions →Code on GitHub. The default for most projects in 2026.
GitLab CI →Code on GitLab. Tight integration; mature container registry.
Jenkins →Self-hosted, plugin-rich, lives in many enterprises.
CircleCI →Hosted, fast, strong macOS/iOS support.
Azure Pipelines →Microsoft / .NET-heavy stacks; deep Azure integration.
Buildkite, Drone, Harness, Argo WorkflowsSpecialized: hybrid runners, Kubernetes-native, enterprise governance.
GitOps (Argo CD, Flux) →Not a CI tool — a CD model where Git is the source of truth for cluster state.
Properties

What Makes a Pipeline Trustworthy

  • Reproducible. Same commit + same pipeline = same artifact, every time. No "works on the build server."
  • Hermetic. Builds don't reach out to the internet at random. Pin versions, use a proxy, lock dependencies.
  • Fast. If CI takes 30 minutes, people batch up changes, which defeats the point. Aim for under 10 minutes for the inner loop.
  • Parallel where possible. Tests, lints, builds — run them concurrently, fail the whole pipeline on the first failure.
  • Cached intelligently. Dependencies, build outputs, container layers. Don't recompute the world on every push.
  • Idempotent. Re-running a step shouldn't double-publish an artifact or double-deploy.
  • Observable. Logs, durations, failure reasons — a flaky test is a fixable problem, but only if you can find it.
Artifacts & Promotion

Build Once, Deploy Many

A core CI/CD principle: build the artifact once, then promote the same bytes through staging → production. Never rebuild for prod.

  • Immutable tags. Tag images by commit SHA, not latest. latest is a lie waiting to happen.
  • Pin by digest in production. image@sha256:… beats image:v1.2.3.
  • Promotion = config change, not rebuild. Change which digest the prod environment points to.
  • Provenance. Sign artifacts (Sigstore / cosign), generate SBOMs, attest the build context. Required for supply-chain integrity.
Deploy Safely

How CD Reaches Production

  • Health checks. The deployer waits until the new instance is actually serving before shifting traffic.
  • Progressive rollouts. Rolling, blue/green, canary. Deployment strategies deep dive →
  • Feature flags. Decouple deploy from release. Code can ship to prod dark, then be enabled per-cohort.
  • Automatic rollback. If error rate / latency / SLO burn cross thresholds during rollout, revert without paging anyone.
  • Database changes are special. Expand → migrate → contract; never do schema-breaking changes in the same deploy as the code that needs them.
Secrets & Security

Don't Leak Through the Pipeline

  • Secrets live in a manager (GitHub Secrets, Vault, AWS Secrets Manager) — not in repo or workflow YAML.
  • Short-lived credentials. Use OIDC federation between CI and your cloud. No long-lived keys for the pipeline.
  • Least privilege per job. A test job doesn't need deploy creds.
  • Fork-PR safety. Don't expose secrets to PRs from forks. Most providers split this automatically — don't disable it.
  • Pin actions / images by digest. Tag-mutability means a malicious update can hijack your pipeline.
  • Build provenance & signing. SLSA, Sigstore, in-toto — verifiable chain from source to artifact.
Worked Example

The URL Shortener Pipeline

A minimal but realistic GitHub Actions pipeline that lints, tests, builds a container, deploys a per-PR preview, and on merge promotes the same image to production.

name: ci-cd
on:
  pull_request:
  push: { branches: [main] }

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test -- --coverage
      - uses: aquasecurity/trivy-action@v0.20.0    # SAST + dep scan

  build:
    needs: test
    runs-on: ubuntu-latest
    permissions: { id-token: write, contents: read, packages: write }
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with: { registry: ghcr.io, username: ${{ github.actor }}, password: ${{ secrets.GITHUB_TOKEN }} }
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to:   type=gha,mode=max

  preview:
    if: github.event_name == 'pull_request'
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh preview-${{ github.event.number }} ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

  deploy-prod:
    if: github.ref == 'refs/heads/main'
    needs: build
    environment: production
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh prod ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
      - run: ./smoke-test.sh https://urls.example.com   # auto-rollback on failure

Notice: tests gate the build, the same digest is promoted from preview to prod, deploys are gated by GitHub's environment protections, and a smoke test (with auto-rollback) is the last word.

Common Pitfalls

Pipelines That Fight Their Owners

  • Slow CI. If devs hesitate to push, the pipeline is the bottleneck. Profile, parallelize, cache.
  • Flaky tests merged anyway. One flaky test trains the team to ignore failures. Quarantine flakes; fix or delete within a week.
  • Building separately for staging and prod. Different bytes, different bugs. Build once, promote.
  • Manual approval as a habit. If every deploy needs a human, you don't have CD — you have CI plus paperwork.
  • Snowflake build agents. Self-hosted runners with hand-installed tools drift. Build the runner image; replace, don't patch.
  • Secrets in logs. One echo $TOKEN on a debug day is a public token. Mask consistently.
  • No rollback story. Forward-only is fine until it isn't. Practice rollbacks.
Continue

Related Reading