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.
← Back to DevOpsDORA's research is clear: elite teams deploy multiple times per day with lower change-failure rate than teams who deploy quarterly.
| Model | Idea | When it fits |
|---|---|---|
| Trunk-based | Short-lived branches (< 1 day), merge to main behind feature flags. | The default for CI/CD-driven teams. Pairs naturally with CD. |
| GitHub Flow | main + short feature branches; deploy from main. | Small/mid teams. Effectively trunk-based with PRs. |
| GitFlow | main, develop, release branches, hotfix branches. | Long-release-cycle products. Heavyweight; usually overkill. |
| Release Trains | Cut 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.
Order matters: cheap, fast checks first. A 30-second lint failure beats waiting 12 minutes for the build to fail on a typo.
| Tool | Sweet 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 Workflows | Specialized: 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. |
A core CI/CD principle: build the artifact once, then promote the same bytes through staging → production. Never rebuild for prod.
latest. latest is a lie waiting to happen.image@sha256:… beats image:v1.2.3.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.
echo $TOKEN on a debug day is a public token. Mask consistently.