CI / CD#

Continuous Integration runs on every push: build, test, lint, package. Continuous Delivery / Deployment promotes the resulting artifact through environments to production. The two share infrastructure but answer different questions: CI = “is this change correct?”, CD = “is this change running where it belongs?”.

        flowchart LR
  Dev[Commit / PR] --> CI[CI]
  CI --> Lint[Lint]
  CI --> Unit[Unit Tests]
  CI --> Int[Integration Tests]
  Lint --> Build[Build Artifact]
  Unit --> Build
  Int --> Build
  Build --> Sign[Sign + SBOM]
  Sign --> Reg[(Registry)]

  Reg --> Dev2[Dev]
  Dev2 -->|"promote"| Stage[Staging]
  Stage -->|"smoke + canary"| Prod[Production]

  Prod -.->|metrics + alerts| Obs[Observability]
  Obs -.->|rollback signal| Stage
    

Pipelines#

The structural unit of CI/CD is a pipeline, a directed graph of steps with caching, artifacts, and parallelism. The platforms below all express the same shape with different syntax. GitHub Actions and GitLab CI dominate; Jenkins endures in legacy estates; Argo Workflows and Tekton run on Kubernetes.

A pipeline is a directed graph of steps with caching, artifacts, and parallelism between them. Common platforms:

A small GitHub Actions workflow:

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with: { go-version: '1.22' }
      - run: go test ./...
      - run: go vet ./...

  build:
    needs: test
    runs-on: ubuntu-latest
    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/myorg/app:${{ github.sha }}

Artifacts#

Pipelines produce immutable artifacts that move through environments:

  • Container images, addressed by digest.

  • Language-native packages: jars, wheels, npm tarballs, gem files.

  • OCI artifacts (Helm charts, SBOMs) in container registries.

  • Binaries with checksums and signatures.

Best practice is to build once, deploy many times. Don’t rebuild for each environment.

Deployment Strategies#

The five shapes of rollout. Recreate accepts downtime; rolling update is Kubernetes’ default; blue/green runs two production environments and switches; canary shifts traffic gradually with health checks; feature flags decouple deployment from release entirely.

  • Recreate, stop old, start new. Simplest; has downtime.

  • Rolling update, replace instances in batches. Default in Kubernetes.

  • Blue / green, run two production environments; cut traffic over.

  • Canary, send a small fraction of traffic to the new version, expand if healthy.

  • Feature flags, decouple deployment from release; toggle behavior without redeploying.

GitOps#

The desired state of every environment lives in a Git repository. A controller (Argo CD, Flux) reconciles the cluster to match. Pull requests become the change-management mechanism.

  • Auditable: every change is a commit.

  • Roll back by reverting.

  • Same workflow across environments.

Pipeline Hygiene#

The discipline that turns a working pipeline into a trustworthy one. Aggressive caching, parallel test execution, fast-fail ordering, version pinning by digest, artifact signing, and secret hygiene; each is a small investment that pays back many times over.

  • Cache aggressively, npm, maven, Go modules, Docker layers.

  • Run tests in parallel, shard by package or test file.

  • Fail fast, run linters and unit tests before integration tests.

  • Pin versions, pin actions, base images, and dependencies by digest.

  • Sign and verify artifacts, cosign, Sigstore, in-toto attestations.

  • Keep secrets out of logs, mask, scope, and rotate.