Setting Up CI/CD for Small Teams
CI/CD Without the Complexity
Enterprise CI/CD pipelines can be intimidating. Multiple environments, complex approval workflows, and infrastructure that requires dedicated DevOps engineers to maintain.
But small teams don't need enterprise complexity. Here's how to set up effective CI/CD that a small team can actually maintain.
The Minimal Viable Pipeline
Every team should have at minimum:
- Automated tests on every push
- Automated deployment to staging on merge to main
- One-click production deployment
That's it. Start here and add complexity only when you need it.
GitHub Actions: The Sweet Spot
For most small teams, GitHub Actions hits the right balance of power and simplicity. Here's a production-ready workflow:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run tests
run: npm test
- name: Build
run: npm run build
deploy-staging:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Staging
run: |
# Your deployment command here
# e.g., vercel --prod --scope=stagingKey Principles for Small Teams
1. Keep Pipelines Fast
If your pipeline takes more than 10 minutes, developers will avoid pushing frequently. Optimize ruthlessly:
- Cache dependencies
- Run tests in parallel
- Skip unnecessary steps in PR builds
- name: Cache node modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}2. Fail Fast, Fail Clearly
Order your jobs so the fastest checks run first:
- Linting (seconds)
- Type checking (seconds)
- Unit tests (seconds to minutes)
- Integration tests (minutes)
- E2E tests (minutes)
3. Make Staging Mirror Production
Your staging environment should be as close to production as possible:
- Same runtime versions
- Same environment variables (with test values)
- Same infrastructure configuration
4. Automate Everything Except Production
Automatic staging deploys give you confidence. Manual production deploys give you control.
deploy-production:
needs: [test, deploy-staging]
if: github.ref == 'refs/heads/main'
environment: production # Requires manual approval
runs-on: ubuntu-latest
steps:
- name: Deploy to Production
run: |
# Production deploymentDatabase Migrations in CI/CD
Handle database changes carefully:
- name: Run migrations
run: |
npm run db:migrate
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}For small teams, I recommend:
- Run migrations as part of deployment
- Always make migrations reversible
- Test migrations on staging first
Secrets Management
Never commit secrets. Use GitHub's encrypted secrets:
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}For local development, use a .env.local file (git-ignored).
Monitoring Your Pipeline
Track these metrics:
- Build time - Should stay under 10 minutes
- Success rate - Should be above 90%
- Deployment frequency - Should increase over time
Common Pitfalls to Avoid
- Over-engineering - Don't add Kubernetes for a two-person team
- Skipping staging - "It works on my machine" isn't a deployment strategy
- Manual steps - If it's manual, it will be forgotten
- Ignoring flaky tests - Fix or delete them, don't just re-run
Getting Started Today
- Create
.github/workflows/ci.ymlwith the template above - Set up your staging environment
- Configure secrets in GitHub
- Push to main and watch it work
You can have a working CI/CD pipeline in an afternoon. The investment pays dividends in confidence and velocity from day one.