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.
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: ~15minIf 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 --noEmitZero 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_SHATag 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 stagingStaging 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 onlyRolling 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.
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:abc123Every 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
developbranch. Noreleasebranches. Nostagingbranch.
Every PR gets a preview environment on RaidFrame:
# Automatic preview for every PR
rf preview --pr 123
# → https://pr-123.preview.raidframe.appMetrics 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.
Ship faster with RaidFrame
Auto-scaling compute, managed databases, global CDN, and zero-config CI/CD. Free tier included.