DevSecOps Done Right: Building a Complete CI/CD Pipeline
From code to production with confidence.
The Problem: Late-Stage Security
Traditional development workflows treat security as a gate at the end. Code gets written, features get built, and then - right before release - someone runs a security scan and finds 47 critical vulnerabilities.
This is expensive. Fixing security issues in production costs 30x more than fixing them during development. The solution? Shift security left.
The ZAYN Books Project
To demonstrate modern DevSecOps practices, I built a fullstack book management application. The app itself is simple - React frontend, Express backend, SQLite database. But the pipeline is where the magic happens.
The Pipeline Architecture
┌─────────────┐ ┌─────────────┐
│ 🔍 Lint │ │ 🛡️ Security │ ← Parallel
└──────┬──────┘ └──────┬──────┘
└───────┬───────┘
▼
┌──────────────┐
│ 🧪 Unit Tests │ ← Vitest
└──────┬───────┘
▼
┌──────────────┐
│ 📡 API Tests │ ← Newman/Postman
└──────┬───────┘
▼
┌───────┬───────┬───────┐
│Chrome │Firefox│WebKit │ ← E2E (Playwright)
└───┬───┘└───┬───┘└───┬─┘
└────────┼────────┘
▼
┌──────────────┐
│ 🏗️ Build │ ← Production bundle
└──────────────┘Stage 1: Parallel Quality Gates
Linting (Biome) and security scanning (Semgrep + npm audit) run in parallel. If either fails, the pipeline stops immediately. No point running expensive tests on bad code.
Stage 2: Unit Tests
Vitest runs the backend unit tests - checking business logic, data validation, and database operations. These are fast (under 10 seconds) and catch logic errors early.
Stage 3: API Tests
Newman runs a Postman collection against the running server. This tests the actual HTTP interface - request/response formats, status codes, error handling.
Stage 4: Cross-Browser E2E
Playwright runs end-to-end tests on Chromium, Firefox, and WebKit simultaneously. These test the full user flow: adding books, editing, deleting, searching.
test('can add a new book', async ({ page }) => {
await page.goto('/');
await page.fill('[data-testid="title"]', 'The DevOps Handbook');
await page.fill('[data-testid="author"]', 'Gene Kim');
await page.click('[data-testid="submit"]');
await expect(page.locator('text=The DevOps Handbook')).toBeVisible();
});Stage 5: Production Build
Only after all tests pass does the production bundle get built. This ensures that what gets deployed has been thoroughly validated.
Security Measures
- Input Validation: All user input sanitized before processing
- Prepared Statements: SQL injection prevention built into the ORM
- CORS Configuration: Only the frontend origin can access the API
- Dependency Scanning: npm audit catches vulnerable packages
- Static Analysis: Semgrep catches insecure code patterns
CI/CD Configuration
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm audit --audit-level=high
- uses: returntocorp/semgrep-action@v1
test:
needs: [lint, security]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:unit
- run: npm run test:api
- run: npx playwright install
- run: npm run test:e2eResults
With this pipeline in place, every pull request is automatically validated for:
- Code quality and formatting
- Known security vulnerabilities
- Correct business logic
- API contract compliance
- Cross-browser compatibility
Developers get feedback in minutes, not days. Security issues are caught before they reach production. And the release process is fully automated.
Enjoyed this article?