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.