EngineeringCI/CDDevOpsdeployment

CI/CD Pipeline Best Practices for Production Deployments

How to build a CI/CD pipeline that catches bugs before production, deploys without downtime, and doesn't take 45 minutes to run. Practical patterns from real production systems.

R

RaidFrame Team

October 4, 2025 · 5 min read

A good CI/CD pipeline is your last line of defense between a developer's commit and production. A bad one is a 45-minute bottleneck that everyone ignores.

Here's how to build one that's fast, reliable, and actually catches problems.

The ideal pipeline

Push → Lint → Test → Build → Security Scan → Deploy Staging → Deploy Production
       2min   5min   3min     1min            2min             2min
                                                        Total: ~15min

If your pipeline takes longer than 15 minutes, developers will stop waiting for it and merge anyway.

Stage 1: Lint and format

Run linters first because they're fast and catch obvious issues.

lint:
  - eslint . --max-warnings 0
  - prettier --check .
  - tsc --noEmit

Zero warnings policy. Warnings that nobody fixes accumulate until they're meaningless noise.

TypeScript type checking as a lint step catches entire categories of bugs that tests miss.

Total time: 1-2 minutes

Stage 2: Test

Run tests in parallel. Split by type:

test-unit:
  - vitest run --coverage
  parallel: true
 
test-integration:
  - vitest run --config vitest.integration.config.ts
  needs: [database-service]
 
test-e2e:
  - playwright test
  needs: [app-service]

Speed tips

  • Parallelize. Run unit, integration, and E2E tests concurrently.
  • Shard large test suites. Split E2E tests across 4 workers.
  • Cache dependencies. Don't reinstall node_modules on every run.
  • Use test databases. Spin up PostgreSQL in Docker, run migrations, seed data.
  • Skip redundant tests. Only run E2E tests on files that changed (with dependency tracking).

Total time: 3-5 minutes (with parallelization)

Stage 3: Build

Build your production artifact — Docker image, static bundle, whatever your app ships as.

# Build
docker build -t myapp:$GIT_SHA .
 
# Tag
docker tag myapp:$GIT_SHA registry.raidframe.com/myapp:$GIT_SHA
docker tag myapp:$GIT_SHA registry.raidframe.com/myapp:latest
 
# Push
docker push registry.raidframe.com/myapp:$GIT_SHA

Tag with git SHA, not "latest." Every deployment should be traceable to an exact commit.

Cache Docker layers. Use BuildKit cache mounts for node_modules and build caches.

Total time: 2-3 minutes (with layer caching)

Stage 4: Security scan

Scan for known vulnerabilities before they reach production.

security:
  - npm audit --production --audit-level=high
  - trivy image myapp:$GIT_SHA --severity HIGH,CRITICAL
  - gitleaks detect --source . --no-banner
  • Dependency audit — known CVEs in your packages
  • Container scan — vulnerabilities in base images and system packages
  • Secret detection — accidentally committed API keys, passwords, tokens

Don't block on low/medium vulnerabilities or your pipeline will be permanently red. Block on high and critical only.

Total time: 1 minute

Stage 5: Deploy staging

Every change goes to staging before production. No exceptions.

deploy-staging:
  - rf deploy --env staging --image myapp:$GIT_SHA
  - rf health-check --env staging --wait 60s
  - rf run-smoke-tests --env staging

Staging should be a near-exact copy of production:

  • Same infrastructure (containers, databases, caches)
  • Same configuration (except secrets and domains)
  • Production-like data volume (anonymized production data or realistic seeds)

Smoke tests after staging deploy verify critical paths work: authentication, core API endpoints, database connectivity.

Total time: 2-3 minutes

Stage 6: Deploy production

Production deployment should be boring. If it's stressful, your pipeline isn't good enough.

deploy-production:
  - rf deploy --env production --image myapp:$GIT_SHA --strategy rolling
  - rf health-check --env production --wait 120s
  needs: [deploy-staging]
  when: main branch only

Rolling deployments

New instances start, get health-checked, receive traffic. Old instances drain connections and shut down. Zero downtime.

Canary deployments

Deploy to 5% of traffic first. Monitor error rates for 10 minutes. If clean, promote to 100%. If not, automatic rollback.

Blue/green deployments

Deploy entirely new environment, switch traffic at the load balancer level. Instant rollback by switching back.

For most teams, rolling deployments are sufficient. Use canary for high-stakes services.

Try RaidFrame free

Deploy your first app in 60 seconds. No credit card required.

Start free

Rollback strategy

When things go wrong (and they will):

# Immediate rollback to previous version
rf rollback --env production
 
# Rollback to specific version
rf deploy --env production --image myapp:abc123

Every deploy should be rollbackable in under 60 seconds. If it's not, you'll hesitate to deploy, and deployment frequency drops, and batch sizes grow, and risk increases.

Branch strategy

Keep it simple:

  • main — always deployable, deploys to production on merge
  • Feature branches — short-lived (1-3 days), deployed to preview environments
  • No develop branch. No release branches. No staging branch.

Every PR gets a preview environment on RaidFrame:

# Automatic preview for every PR
rf preview --pr 123
# → https://pr-123.preview.raidframe.app

Metrics to track

  • Pipeline duration — should be <15 minutes. Alert if >20.
  • Deployment frequency — aim for daily. Weekly is the minimum.
  • Failure rate — <5% of deployments should require rollback.
  • Recovery time — time from "something's wrong" to "it's fixed." Target <30 minutes.

CI/CD on RaidFrame

RaidFrame has built-in CI/CD that triggers on git push:

git push origin main
# → Build, test, deploy. Automatic.

No GitHub Actions YAML. No Jenkins. No CircleCI configuration. Push code, it deploys.

CI/CDDevOpsdeploymentautomation

Ship faster with RaidFrame

Auto-scaling compute, managed databases, global CDN, and zero-config CI/CD. Free tier included.