Building a CI Pipeline
Run tests, lint, and build artifacts automatically on every push and pull request
Free tier: read the explanation here. Upgrade to Pro for Drills, Speed challenges & Mastery badges.
UpgradeKnowledge Debt detected
You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.
Explanation
A CI pipeline is the automated sequence that runs on every code change to verify it is safe to merge and deploy.
Standard CI pipeline stages:
1. Checkout code 2. Set up runtime (Python/Node version, cache) 3. Install dependencies 4. Lint (code style and quality) 5. Type check (TypeScript, mypy) 6. Run tests (unit + integration) 7. Build artifact (if tests pass) 8. Publish artifact (Docker push, npm publish, etc.)
A complete Python/FastAPI CI example:
yaml name: CI on: [push, pull_request] jobs: quality: runs-on: ubuntu-latest services: postgres: image: postgres:15 env: POSTGRES_PASSWORD: testpass POSTGRES_DB: testdb ports: ['5432:5432'] options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' cache: 'pip' - run: pip install -r requirements.txt - run: flake8 . --max-line-length=100 name: Lint - run: mypy src/ name: Type check - run: pytest --tb=short -q name: Tests env: DATABASE_URL: postgresql://postgres:testpass@localhost/testdb
Matrix testing — test on multiple versions:
yaml jobs: test: strategy: matrix: python-version: ['3.10', '3.11', '3.12'] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - run: pip install -r requirements.txt && pytest `` Matrix creates one job per combination — all run in parallel.
Uploading test reports:
yaml - run: pytest --junitxml=test-results.xml - uses: actions/upload-artifact@v4 with: name: test-results path: test-results.xml if: always() # upload even if tests fail
Fail fast — exit early on first failure:
yaml strategy: fail-fast: true # cancel remaining matrix jobs if one fails (default) # fail-fast: false # let all matrix jobs finish
Examples
Full-stack CI pipeline (Next.js)
Build runs last — only reached if typecheck, lint, and tests all pass. Fastest feedback first.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Type check
run: npx tsc --noEmit
- name: Lint
run: npm run lint
- name: Run tests
run: npm test
- name: Build
run: npm run build
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}Services: running a real database in CI
The health-cmd option makes the job wait until Postgres is actually ready before running tests — prevents flaky "connection refused" failures.
# .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
services:
db:
image: postgres:15
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: apptest
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.12', cache: pip }
- run: pip install -r requirements.txt
- name: Run migrations
run: alembic upgrade head
env:
DATABASE_URL: postgresql://testuser:testpass@localhost/apptest
- name: Run tests
run: pytest tests/ -v
env:
DATABASE_URL: postgresql://testuser:testpass@localhost/apptestHow well did you understand this?
Next in CI/CD
Deployment Pipelines