Continuous Integration (CI): every change goes through automated build and test. Continuous Delivery (CD): the code is always ready to ship. Continuous Deployment: shipping happens automatically.
Modern software teams almost universally use CI/CD. The patterns and tools have stabilized; the differences are operational.
A typical pipeline:
Each stage gates the next. Failures stop the pipeline.
Modern CI/CD: pipelines defined in code, in the repo:
# .github/workflows/build.yml
name: Build and test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./mvnw clean test
Pros: version controlled with the code; reviewable; reproducible. Cons: tied to the CI tool's syntax.
GitHub Actions, GitLab CI, CircleCI, Jenkins (declarative pipelines) all support this. Avoid older "configure in UI" CI systems.
Just deploy. Simplest. Risky for production.
Replace instances one at a time. New version coexists with old briefly.
Two identical environments. Switch traffic from blue to green. Easy rollback.
Route a small percentage of traffic to the new version. Monitor; expand or roll back.
Deploy code; turn features on/off via flag. Decouple deploy from release. See FeatureToggleManagement.
For most production systems, canary or blue-green are the standards. Feature flags add another layer of control.
The most popular. Well-integrated with GitHub. YAML pipeline definition; reusable workflows.
Tightly integrated with GitLab. Mature; full-featured.
Independent CI. Fast; good developer experience.
Old school but still common. More flexible than the others; more operational overhead.
Hybrid (orchestrator in cloud; agents you run). Popular for performance-sensitive needs.
AWS CodePipeline, GCP Cloud Build, Azure Pipelines. Useful when integrated with cloud workflows.
For most teams, GitHub Actions if on GitHub, GitLab CI if on GitLab. The tool matters less than the pipeline design.
Developers want their tests to fail fast. Order stages by speed:
Failure at lint stage takes seconds; failure at deploy takes much longer.
Independent test groups run in parallel. Reduces total time dramatically.
Build dependencies (npm packages, Maven .m2, Docker layers). Don't re-download every build.
Test against multiple versions (Node 18, 20, 22; Linux + macOS). Run matrix in parallel.
Only rebuild what changed. Modern monorepo tools (Nx, Turborepo, Bazel) support this. See MonorepoVsPolyrepo.
Pipeline must pass before merge. Enforced via branch protection.
Production deploys triggered by main branch merges (or manual approvals for sensitive systems). The path from code to production is automatic.
Build artifacts tagged with git SHA or semver. Same artifact deployed to dev, staging, prod — no rebuild between environments.
Deploy to dev → run tests → promote to staging → run more tests → promote to production. Same artifact through the chain.
Rolling back to the previous version should be a single command or button click. Slow rollback compounds incidents.
Quick health checks after deploy: is the service responding? Can it connect to dependencies? Catches some failures before they affect users.
Automatic comparison of canary metrics to baseline. If canary error rate is higher, auto-rollback. Manual canaries are slow.