Guide
CI/CD pipelines explained: continuous integration, delivery, and deployment
CI/CD (continuous integration and continuous delivery/deployment) is the practice of automating how code moves from a developer's laptop to production. Instead of copying files over SSH on Friday afternoon, a pipeline runs on every push: compile assets, run tests, scan for vulnerabilities, deploy to staging, and — when checks pass — promote to live traffic. Teams that invest in CI/CD ship smaller changes more often, catch regressions before users do, and recover from bad deploys in minutes instead of hours. This guide walks through pipeline stages, the difference between delivery and deployment, environment promotion, deployment strategies, and the operational habits that keep releases boring.
What CI/CD solves
Manual releases scale poorly. As a codebase grows, "it worked on my machine" stops being a joke and becomes an incident. Integration pain compounds: two developers merge unrelated features, both pass local tests, and the combined main branch fails in production because nobody ran the full suite together. CI/CD addresses three recurring failures:
- Integration drift — code that never meets on a shared branch until release week.
- Untested deploy paths — migrations, config changes, and build steps that only run in production.
- Slow feedback — bugs discovered days after the author context-switched away.
A healthy pipeline turns integration into a continuous, low-cost activity. Every merge is a rehearsal for production. When something breaks, you know which commit caused it and you have artifacts to roll back to.
Continuous integration: build and test on every change
Continuous integration (CI) means the main branch always
reflects integratable code. When a developer opens a pull request or merges
to main, automation should:
- Check out the exact commit (reproducible, not "latest on the runner").
- Install dependencies with a lockfile (
package-lock.json,poetry.lock, etc.). - Run linters and formatters — fast, deterministic gates that catch style and obvious bugs.
- Compile or bundle the application.
- Execute unit and integration tests in parallel where possible.
- Produce build artifacts (container images, static site folders, compiled binaries).
CI should fail fast. Put the cheapest checks first: lint before integration tests, integration tests before end-to-end browser suites. A 90-second feedback loop encourages developers to fix issues immediately; a 45-minute pipeline trains them to push and walk away.
What belongs in CI vs later stages
CI validates correctness of the code artifact. Security scans (dependency audit, SAST), license checks, and container image scanning often run here too. Load testing and chaos experiments usually belong in scheduled jobs or pre-production environments — they are too slow and flaky for every commit.
Continuous delivery vs continuous deployment
These terms are often conflated but mean different risk postures:
- Continuous delivery — every merge produces a production-ready artifact. Releasing to users still requires a human approval click (or a scheduled window). Banks, regulated industries, and teams with complex change management often stop here.
- Continuous deployment — the same pipeline automatically promotes passing builds to production without manual gate. Feature flags and strong observability become mandatory because "hold the release" is no longer an option.
Neither is universally better. A static content site with fingerprinted assets and instant rollback is a natural fit for continuous deployment. A payment settlement service with database migrations and financial correctness requirements may prefer continuous delivery with a human sign-off and a canary phase. The goal is repeatability, not maximum automation for its own sake.
Typical pipeline stages
Most pipelines follow a similar spine, even if tool names differ (GitHub Actions, GitLab CI, CircleCI, Jenkins, Buildkite):
1. Source
Triggered by push, pull request, tag, or schedule. Pin the commit SHA; never deploy a floating branch tip without recording which revision shipped.
2. Build
Produce immutable artifacts. For a static site, that might be a
dist/ folder uploaded to object storage. For a Node API, a
Docker image tagged myapp:abc1234. Artifacts should be
content-addressed or tagged with the git SHA so rollbacks are exact.
3. Test
Unit tests mock external dependencies. Integration tests hit real databases in ephemeral containers. End-to-end tests drive a browser against a preview deployment. Flaky tests are pipeline poison — quarantine or fix them; do not retry indefinitely to greenwash failures.
4. Staging / preview
Deploy the artifact to an environment that mirrors production topology: same TLS termination, same environment variable names, scaled-down replicas. Preview URLs per pull request let reviewers click through UI changes before merge. Pair staging deploys with structured logs and traces so failures are diagnosable.
5. Production promotion
Swap traffic to the new version, run database migrations (often as a separate job with backward-compatible steps), and verify health checks. Post-deploy smoke tests hit critical paths: homepage 200, login, payment webhook signature verification, RPC health endpoint.
Deployment strategies
How you cut over traffic determines blast radius when something goes wrong:
Rolling update
Replace instances one at a time behind a load balancer. Simple, works for stateless services. Risk: old and new versions run simultaneously during the roll — incompatible API changes need feature flags or versioned endpoints.
Blue-green
Stand up a full parallel environment ("green"), run checks, then flip the load balancer from blue to green. Instant rollback = flip back. Cost: double infrastructure during cutover.
Canary
Route 1–5% of traffic to the new version, watch error rates and latency for 15–30 minutes, then expand. Catches issues that staging missed because production data volume or geography differs. Requires metrics dashboards and automated rollback triggers.
Static sites and CDNs
Fingerprinted assets (app.a1b2c3.js) can deploy atomically:
upload new files, update HTML pointers, purge CDN cache for HTML only.
Users either get the old or new bundle — no mixed JS/CSS versions. See
CDN caching and purge strategies
for how edge caches interact with deploy frequency.
Secrets, config, and environments
Pipelines need credentials: cloud API keys, database URLs, signing keys for TLS certificates, webhook HMAC secrets. Rules:
- Store secrets in the CI platform's secret manager or a vault — never in the repo.
- Scope secrets per environment; staging credentials must not work in production.
- Inject at runtime, not bake into images. Rotate without rebuilding old artifacts.
- Audit which workflows can read which secrets; fork PRs from untrusted contributors should not access production keys.
Configuration (feature toggles, rate limits, RPC endpoint URLs) belongs in environment variables or a config service — not hardcoded branches in source. Twelve-factor style: build once, configure at deploy time.
Database migrations and backward compatibility
Schema changes are the hardest part of CD for stateful apps. Safe pattern: expand → migrate → contract. Add a nullable column in release N (old code ignores it). Backfill data in release N+1. Switch application code to read the new column. Drop the old column in N+2. Never deploy code that requires a column and the migration in the same atomic second unless you can tolerate downtime.
For APIs consumed by mobile clients or third parties, treat every deploy as a potential breaking change. Version endpoints or maintain backward-compatible response shapes — the same discipline described in REST API design applies to release cadence.
Observability and rollback
A pipeline is only as good as your ability to detect a bad deploy. Minimum bar after every production promotion:
- Error rate and latency dashboards compared to pre-deploy baseline.
- Structured logs searchable by deployment ID or git SHA.
- Alerts on SLO burn rate, not just "CPU high".
- A documented rollback: redeploy previous artifact tag or revert the merge commit.
Rollback should take less than five minutes. If it requires manual database surgery, you do not have continuous deployment — you have continuous hope. Keep the last known-good artifact addressable; container registries and static asset buckets should retain several recent versions.
Common mistakes
- Skipping tests on "hotfix" branches — hotfixes cause the worst outages.
- Deploying directly from a developer laptop "just this once" — bypasses audit trail.
- Shared staging that drifts from production (different env vars, smaller DB, no TLS).
- Flaky tests with
retry: 3masking real failures. - Long-lived feature branches that merge once a month — reintroduces integration pain CI was meant to fix.
- Secrets in build logs — mask output and never echo environment variables in scripts.
- No deployment notifications — teams learn about releases from angry users.
Starter checklist
- Every PR runs lint + unit tests; merges to main are blocked on failure.
- Artifacts tagged with immutable git SHA; production deploys reference that tag.
- Staging environment updated automatically on main merge.
- Production deploy is one command or automatic with canary + rollback.
- Secrets in vault; fork PRs cannot exfiltrate production credentials.
- Post-deploy smoke tests and error-rate monitoring for 30 minutes.
- Runbook documents who rolls back and how, without improvising under pressure.
Related reading
- Observability explained — metrics, logs, traces, and SLOs for deploy confidence
- Event-driven architecture explained — async workers and deploy ordering
- REST API design explained — versioning and backward-compatible changes
- All Solana Garden guides