CI and CD get said together so often they're treated as one concept — they're two distinct practices that happen to fit together well.
Continuous Integration: catching problems early
CI means every code change is automatically built and tested as soon as it's pushed — not batched up and tested right before a release. The core value: a bug is caught within minutes of being introduced, when the change causing it is still fresh in the author's mind, rather than weeks later during a pre-release testing pass when nobody remembers which of fifty commits caused it.
A typical CI pipeline, triggered on every push:
# .github/workflows/ci.yml
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: npm install
- run: npm run lint
- run: npm run type-check
- run: npm test
- run: npm run buildContinuous Delivery vs. Continuous Deployment
This is the split that gets blurred:
- Continuous Delivery — every change that passes CI is ready to deploy, packaged and verified, but a human still decides when to actually release it (often with one click).
- Continuous Deployment — every change that passes CI deploys automatically, with no manual approval step. The pipeline itself is the release mechanism.
Continuous Deployment is the more aggressive of the two and requires real confidence in your test suite — without strong automated coverage, shipping every merged commit straight to production automatically just means bugs reach production faster too.
A typical full pipeline
Push code
│
▼
Lint + type-check ──fail──► Notify, stop
│ pass
▼
Run tests ──fail──► Notify, stop
│ pass
▼
Build artifact
│
▼
Deploy to staging
│
▼
Run smoke tests against staging ──fail──► Notify, stop
│ pass
▼
Deploy to production (manual approval, or automatic)
Where teams commonly get this wrong
Treating "tests pass" as the only gate. A test suite with poor coverage passing doesn't mean the code is safe to ship — it means the parts that are tested didn't break. Pipelines are only as trustworthy as the test suite backing them.
No staging environment, or a staging environment that's drifted from production. Deploying straight from CI to production skips the chance to catch environment-specific issues (config differences, infrastructure quirks) that unit tests can't catch. Even a lightweight staging step catches real problems before real users do.
Slow pipelines that get bypassed. A CI run that takes 25 minutes gets skipped ("I'll just merge, it's a small change") far more than one that takes 3. Parallelizing test suites and caching dependencies aggressively isn't a nice-to-have — a slow pipeline is a pipeline people route around.
The actual goal
The point of CI/CD isn't automation for its own sake — it's shrinking the time between "a change is made" and "we know whether it's safe," and then shrinking the time between "it's safe" and "users have it." Every piece of the pipeline should be justified by one of those two goals, not added because it seemed like standard practice.