
The CI/CD Problem for SMBs
You’ve heard the pitch: “Automate your deployments with CI/CD and ship code faster.” So you set up a basic GitHub Actions workflow, push to main, and… it works. For a while.
Then your team grows from 2 to 8 engineers. The monolith becomes a microservice. Dependencies multiply. And suddenly your “simple” pipeline is:
- Taking 45 minutes to run (and failing halfway through)
- Deploying to production on Fridays because that’s the only time it works
- Breaking other teams’ deployments because there’s no isolation
- Requiring manual approval steps that bottleneck the entire process
You need a production-grade CI/CD pipeline. But you don’t have a dedicated DevOps engineer or weeks to build one from scratch.
Good news: you can go from zero to production-grade CI/CD in one week using proven patterns. Here’s exactly how.
Day 1: Audit Your Current State
Before building anything, understand what you have:
CI/CD Maturity Checklist
- ☐ Source code in Git (if not, stop here and fix this first)
- ☐ Automated tests exist (unit, integration, or both)
- ☐ Infrastructure is defined as code (Terraform, Pulumi, CloudFormation)
- ☐ Secrets are managed (not hardcoded in repos)
- ☐ Deployments are currently manual or semi-automated
- ☐ You have at least one staging/QA environment
If you don’t have all of these, that’s fine. This guide will get you there. Check out our Level 1: Surviving Chaos post for the fundamentals.
Day 2: Set Up Your Pipeline Foundation
Choose Your CI/CD Platform
For SMBs, we recommend one of these three (all have generous free tiers):
| Platform | Free Tier | Best For |
|---|---|---|
| GitHub Actions | 2,000 min/month free | GitHub-native, easiest setup |
| GitLab CI/CD | 400 min/month free | Self-hosted runners, container registry |
| Jenkins | Fully free (self-hosted) | Complex multi-platform pipelines |
Start with GitHub Actions — it’s the most accessible for SMBs and integrates natively with your repos.
Basic Production Pipeline (GitHub Actions)
# .github/workflows/production.yml
name: Build, Test & Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from test
build-and-push:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
deploy:
needs: build-and-push
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: |
ssh deploy@${{ secrets.HOST }} "
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
docker compose -f /app/docker-compose.yml up -d
"
Day 3: Add Quality Gates and Security
Production-grade pipelines don’t just deploy — they protect. Add these non-negotiable gates:
Required Checks
- Linting — ESLint, Ruff, or Pylint to enforce code standards
- Unit tests — Minimum 70% coverage for critical paths
- Integration tests — Test against a real database in CI
- Dependency scanning — Dependabot or Trivy for vulnerability detection
- Secret scanning — Prevent accidental credential exposure
# Add security scanning to your pipeline
- name: Vulnerability scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'HIGH,CRITICAL'
Day 4: Implement Deployment Strategies
Basic deployments cause downtime. Production-grade deployments use zero-downtime strategies:
Blue-Green Deployment
Maintain two production environments. Route traffic to the new one only after health checks pass. If something fails, switch back instantly.
# Docker Compose blue-green setup
version: '3.8'
services:
app-blue:
image: myapp:${VERSION}
ports: ["3001:3000"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
app-green:
image: myapp:${VERSION}
ports: ["3002:3000"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
nginx:
image: nginx:alpine
ports: ["80:80"]
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
Day 5: Monitoring and Rollback
A production pipeline is incomplete without:
- Deployment notifications — Slack/email alerts on failure or success
- Automated rollback — If health checks fail after deployment, revert to the previous version automatically
- Deployment dashboard — Track deploy frequency, lead time, change failure rate, and MTTR (the four key DORA metrics)
# Automated rollback script
#!/bin/bash
echo "Checking deployment health..."
sleep 30 # Wait for the app to stabilize
if curl -f http://localhost:3000/health; then
echo "Deployment healthy!"
else
echo "Health check failed! Rolling back..."
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prev.yml up -d
echo "Rolled back to previous version"
exit 1
fi
Measuring Success: Your Day 7 Checkpoint
By the end of the week, your pipeline should:
- Run in under 10 minutes from push to production
- Include automated tests + security scanning in every run
- Support zero-downtime deployments
- Have automatic rollback on health check failure
- Notify your team in Slack or email on every deployment
This isn’t the end — it’s the beginning. As your team grows, you can add canary deployments, feature flags, and progressive delivery. But this foundation will eliminate the most common deployment failures that plague SMBs.
And if you’d rather not build it yourself? We design production-grade CI/CD pipelines for SMBs — fully set up in under a week, including training for your team.
Need help implementing this in your company?
We help SMBs adopt these practices without hiring a full-time internal team.
Book a free consultation and discover how we can transform your infrastructure.