← All insights

CI/CD pipeline patterns that survive real teams

Small conventions — trunk-based flow, environment parity, and artifact discipline — that keep releases boring in a good way.

Boring releases are a feature. The teams that ship confidently and frequently aren't doing anything magical — they've made a set of deliberate architectural choices that eliminate the categories of failure that make deployments stressful.

This post covers the pipeline patterns that hold up across different tech stacks and team sizes.

One trunk, short-lived branches

Integrate to the main line often. Long-lived branches multiply merge pain and hide integration risk. When a branch diverges for two weeks, merging it is a full-day project. When branches live for hours or a day, merges are trivial.

Pair trunk-based development with feature flags when you need to ship code before exposing behavior. A dark launch — code in production, feature disabled — lets you validate deployment without exposing users to incomplete functionality.

A minimal feature flag implementation:

# config.py
FEATURE_FLAGS = {
    "new_checkout_flow": os.getenv("FF_NEW_CHECKOUT", "false").lower() == "true",
    "ai_recommendations": os.getenv("FF_AI_RECS", "false").lower() == "true",
}

# usage
if feature_flags.is_enabled("new_checkout_flow", user_id=current_user.id):
    return render_new_checkout()
else:
    return render_legacy_checkout()

For more sophisticated targeting — percentage rollouts, per-user flags, real-time toggles — use a dedicated feature flag service rather than environment variables.

Build once, promote artifacts

The same build artifact should flow from CI through staging to production. Re-building for each stage invites "works on my machine" drift — the test run passes on the CI-built image, but production runs a slightly different image built later from a different state.

The discipline:

  • Build the container image in CI. Tag it with the commit SHA.
  • Push to a container registry. Never rebuild.
  • Staging deploys that SHA. Production promotes that same SHA.

Record provenance in the image label:

LABEL org.opencontainers.image.revision="abc123def456"
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL build.pipeline_run="12345"
LABEL build.timestamp="2025-03-01T10:22:44Z"

This means any deployed instance can be traced back to an exact commit in under a minute.

Pin dependencies. Lock files (package-lock.json, Pipfile.lock, go.sum) should be committed and treated as first-class artifacts. A build that resolves ^1.2.0 today may resolve differently next month. Pin.

Make failures obvious and fast

The order of checks in your pipeline determines how quickly teams learn about problems. Front-load cheap, fast checks. Push expensive, slow checks toward the end.

A well-ordered pipeline stage:

Stage 1 (< 2 min): lint, type-check, security dependency scan
Stage 2 (< 5 min): unit tests, component tests
Stage 3 (< 10 min): integration tests, contract tests
Stage 4 (< 20 min): end-to-end tests, performance smoke tests
Stage 5: deploy to staging → acceptance gate → deploy to production

Fail fast. If lint fails, don't run integration tests. If unit tests fail, don't build the container. Every minute of wasted CI time adds up across a team over a year.

Surface flaky tests as a first-class problem. A flaky test — one that passes and fails non-deterministically — is not a minor annoyance. It erodes trust in the entire pipeline. Teams start ignoring red builds. That's how real failures get missed. Track flakiness rates per test; quarantine and fix tests above a 2% flake rate.

Environment parity

Environments that diverge silently produce surprises at go-live. The three most common divergences:

  1. Configuration drift. Production has environment variables that staging doesn't. Use infrastructure-as-code for all environment configuration. Every environment is defined in code; diffs are reviewed.

  2. Data migration gaps. A schema migration runs cleanly on staging's small dataset but times out on production's 50M row table. Test migrations on a production-sized dataset. Use online DDL tools for large table modifications.

  3. Dependency version drift. Staging runs Redis 6.2, production runs Redis 7.0. Or worse — the versions are nominally the same but the configuration differs. Pin infrastructure versions in your IaC the same way you pin application dependencies.

Deployment strategies: match risk to technique

Not every deployment needs the same strategy.

Rolling deployment — replace instances of the old version gradually with the new version. Zero downtime, simple to implement, but both old and new code run simultaneously during the transition. Requires backward-compatible changes.

Blue-green deployment — maintain two identical production environments; switch traffic at the load balancer. Instant rollback (flip traffic back). Requires double the infrastructure during the switchover window.

Canary deployment — route a small percentage of traffic (1%, 5%, 10%) to the new version. Monitor error rates and latency before promoting. Best for changes with user-visible risk. Requires solid observability to know when to promote versus rollback.

For most web applications:

  • Use rolling deployments for routine changes.
  • Use canary deployments for changes that affect core user flows.
  • Use blue-green deployments for major infrastructure changes or database migrations.

The rollback plan is part of the deployment plan

Every deployment should have a defined rollback path before it goes out.

Questions to answer before each significant deployment:

  • What does a rollback look like, and how long does it take?
  • Does this change include a database schema migration? Is it reversible?
  • If we roll back the application, will the old version work with the current database schema?
  • Is there a feature flag that can disable the change without a full rollback?

Deployments that can be rolled back in two minutes are different from deployments that require a 45-minute database restore. Know which kind you're doing.

Observability is the other half of CI/CD

A pipeline that deploys successfully but can't tell you whether the deployed version is healthy is incomplete. The deployment gate is not the end of the CI/CD process — it is the midpoint.

Wire observability into the pipeline:

  • Deployment markers in your metrics system (a vertical line on every time-series graph showing when a deployment happened).
  • Automated smoke tests triggered post-deployment against the live environment.
  • Error rate comparison — if P99 latency increases by more than X% within 5 minutes of deployment, trigger an automatic rollback or alert.

The CI/CD pipeline should tell you not just "the deployment succeeded" but "the deployment is healthy."


Explore more under CI/CD & DevOps on the insights hub.