CI/CD for Small Teams: Simple Git Push Deployment That Just Works
Test + deploy in under 3 minutes. GitHub Actions + webhook deployment for teams of 1–5 developers.
In This Guide
CI/CD for Small Teams: Simple Git Push Deployment That Just Works
CI/CD ("Continuous Integration, Continuous Deployment") sounds like something only companies with dedicated DevOps engineers can afford to build. Enterprise diagrams with Kubernetes clusters, ArgoCD, Terraform, and eight different YAML files have made it seem complex by default.
For a small team of 1–5 developers, CI/CD doesn't need to be complex. It needs to work reliably and get out of your way. Here's how to build a CI/CD pipeline that a 2-person team can set up in an afternoon and maintain without a dedicated infrastructure engineer.
What CI/CD Actually Means for a Small Team
Continuous Integration (CI):
Every time a developer pushes code, automated tests run. If tests fail, the developer is notified immediately. Broken code doesn't merge to the main branch.
Continuous Deployment (CD):
Every time code is merged to main, it deploys to production automatically. No manual "deployment steps" that someone has to remember to do.
What this prevents:
- "It worked on my machine" bugs caught before they reach production
- Manual deployment steps that get skipped under deadline pressure
- "Who deployed what and when?" mysteries in post-incident debugging
- Accumulated changes that are hard to roll back
For a small team, CI/CD is not about scale — it's about confidence. You can push code on a Friday afternoon and know it won't break the production site unless your tests are also broken.
The Simplest CI/CD Stack That Works
For a small team (1–5 developers), the minimum viable CI/CD pipeline uses two tools:
- GitHub Actions — free CI/CD built into GitHub
- ApexWeave — managed hosting with webhook-triggered deployments
Total cost: $0 for GitHub Actions (free for public repos; private repos get 2,000 minutes/month free). ApexWeave hosting cost per app.
Setup time: 30–60 minutes for your first pipeline.
Step 1: The Basic Pipeline Structure
Developer pushes code
↓
GitHub Actions triggers
↓
Tests run
↓
If tests pass → Deploy to production
If tests fail → Notify developer, block deployment
↓
Production updated automatically
Create .github/workflows/ci-cd.yml in your repository root:
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
# Step 1: Run tests on every push and PR
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
env:
NODE_ENV: test
# Test-specific env vars only
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
# Step 2: Deploy only on push to main, only if tests passed
deploy:
name: Deploy to Production
needs: test # Waits for test job to succeed
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Trigger deployment
run: |
curl -X POST \
-H "Content-Type: application/json" \
"${{ secrets.DEPLOY_WEBHOOK_URL }}"
- name: Notify team of deployment
run: |
echo "Deployed: ${{ github.event.head_commit.message }}"
echo "Commit: ${{ github.sha }}"
echo "Author: ${{ github.event.head_commit.author.name }}"
Step 2: Setting Up Secrets
Your workflow file should never contain actual API keys, webhook URLs, or credentials. These go in GitHub Secrets.
Add your deployment webhook URL:
1. ApexWeave dashboard → your app → Git & Deploy tab → copy Webhook URL
2. GitHub repo → Settings → Secrets and variables → Actions → New repository secret
3. Name: DEPLOY_WEBHOOK_URL
4. Value: paste your webhook URL
5. Save
Add other secrets your tests need:
- TEST_DATABASE_URL — connection string for your test database
- TEST_API_KEY — any API keys needed for integration tests
Step 3: Branch Strategy for Small Teams
A simple branching strategy that works with this CI/CD setup:
main ← production deployments auto-trigger from here
feature/* ← developer branches (tests run on PR, no deployment)
hotfix/* ← emergency fixes (can bypass staging with team approval)
Workflow for a new feature:
# Create feature branch
git checkout -b feature/user-authentication
# Develop and commit
git add .
git commit -m "Add JWT authentication"
git push origin feature/user-authentication
# Open PR → tests run automatically
# Review and merge → production deploys automatically
Workflow for a bug fix:
git checkout -b fix/checkout-validation-error
# ... fix the bug ...
git commit -m "Fix: validate email before submitting checkout"
git push origin fix/checkout-validation-error
# Open PR → tests run → merge → production deploys
Adding a Staging Environment
Once you have basic CI/CD working, add a staging environment for testing before production.
Setup:
1. Create a second ApexWeave app for staging
2. Add a staging branch to your repository
3. Add a second webhook secret for staging: STAGING_WEBHOOK_URL
Updated workflow:
on:
push:
branches: [main, staging]
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22', cache: 'npm' }
- run: npm ci
- run: npm test
deploy-staging:
name: Deploy to Staging
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/staging' && github.event_name == 'push'
steps:
- name: Deploy to staging
run: curl -X POST "${{ secrets.STAGING_WEBHOOK_URL }}"
deploy-production:
name: Deploy to Production
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Deploy to production
run: curl -X POST "${{ secrets.DEPLOY_WEBHOOK_URL }}"
Your workflow now:
# Feature development
git checkout -b feature/new-dashboard
# Test on staging first
git push origin feature/new-dashboard:staging
# Staging deploys → QA reviews at staging.yourdomain.com
# Promote to production
git checkout main
git merge feature/new-dashboard
git push origin main
# Production deploys automatically
Language-Specific Test Configuration
Node.js (Jest/Vitest)
- name: Run tests
run: npm test -- --coverage
env:
NODE_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb
JWT_SECRET: test-secret-key
# Add PostgreSQL service for integration tests
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
ports:
- 5432:5432
Python (pytest)
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
run: pytest --tb=short -q
env:
DJANGO_SETTINGS_MODULE: myproject.settings.test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb
SECRET_KEY: test-secret-key
PHP (Laravel + PHPUnit)
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: pdo, pdo_mysql, mbstring
- name: Install Composer dependencies
run: composer install --no-interaction --prefer-dist
- name: Copy .env.testing
run: cp .env.testing .env
- name: Generate app key
run: php artisan key:generate
- name: Run migrations
run: php artisan migrate --env=testing
- name: Run tests
run: php artisan test --parallel
Ruby on Rails (RSpec)
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Set up database
run: |
bundle exec rails db:create RAILS_ENV=test
bundle exec rails db:schema:load RAILS_ENV=test
- name: Run tests
run: bundle exec rspec --format progress
env:
RAILS_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb
Deployment Notifications (Highly Recommended)
Know immediately when a deployment succeeds or fails:
Slack notification:
- name: Deployment success
if: success()
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "✅ *Deployed to production*\n*Commit:* ${{ github.event.head_commit.message }}\n*Author:* ${{ github.event.head_commit.author.name }}\n*SHA:* `${{ github.sha }}`"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
- name: Deployment failure
if: failure()
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "❌ *Deployment FAILED*\n*Commit:* ${{ github.event.head_commit.message }}\n*Author:* ${{ github.event.head_commit.author.name }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View failed run>"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
When to Add More to Your Pipeline
Start minimal. Add complexity only when you have a specific problem that simpler alternatives don't solve.
Your current pain → What to add:
| Pain Point | Addition |
|---|---|
| Tests are slow (>5 min) | Parallelise: test --shard=1/4 etc. |
| Different features conflict | Feature flags (env vars) instead of branches |
| Need to test against multiple DB versions | Matrix strategy in GitHub Actions |
| Production deploys during business hours | Schedule deploys: cron: '0 2 * * *' |
| Security vulnerabilities in dependencies | npm audit or snyk step |
| Code style inconsistency | ESLint/Prettier/phpstan in CI |
| Performance regressions | Lighthouse CI step |
What you probably don't need:
- Kubernetes, Helm charts, ArgoCD — for a team under 20 people
- Separate build/push Docker images to registry — managed platforms handle this
- Terraform or Pulumi for infrastructure — managed hosting eliminates this layer
- Multiple staging environments — one staging + production is enough for most teams
What Your Pipeline Tells You
After this CI/CD setup is running, your team gains operational clarity you didn't have before:
From the GitHub Actions dashboard:
- Every commit's test result — green or red, immediately
- Deployment history with commit SHAs and timestamps
- Which branch triggered each deployment
- How long tests and deployments take (track this over time — if it's growing, address it)
From the ApexWeave dashboard:
- Every deployment with build logs
- Rollback history (which deploys were rolled back and when)
- Uptime history (did a deployment cause downtime?)
- Activity log (all environment variable changes, domain updates)
Combined: Full audit trail. When something breaks in production, you know exactly which commit caused it, who made the change, when it deployed, and you can roll back in one click.
The One Thing Most Small Teams Get Wrong About CI/CD
Most teams set up CI/CD and then add too many steps to the pipeline over time: linting, security scans, integration tests, performance tests, end-to-end tests, dependency audits...
The pipeline becomes slow (10+ minutes) and teams start merging to main without waiting for CI. When the pipeline takes longer than a developer's patience, it stops being a safety net.
Keep your CI pipeline under 3 minutes. For small apps:
- Unit tests: 30–60 seconds
- Integration tests (with a real test database): 60–120 seconds
- Build: 30–60 seconds
- Deploy webhook: 2–5 seconds
If your tests take longer, parallelise them or split them across jobs that run concurrently.
Fast CI/CD is used CI/CD. Slow CI/CD gets bypassed.
Set up your deployment target at apexweave.com/git-deployment.php — webhook auto-deployment, build logs, rollback, and activity audit for every push.
Deploy Your App with Git Push
Automatic builds, environment variables, live logs, rollback, and custom domains. No server management required.
Deploy Free — No Card RequiredDeploy Your App with Git Push
Automatic builds, environment variables, live logs, rollback, and custom domains. No server management required.
Deploy Free — No Card RequiredPowered by WHMCompleteSolution