What is DevSecOps?
Traditional development looks like this:
Developers build → QA tests → Security audits → Operations deploys
(weeks) (days) (days) (hours)
DevSecOps integrates security and operations into every step of development:
Code → Lint → Test → Security Scan → Build → Deploy → Monitor
↑ ↑ ↑ ↑ ↑ ↑
Automated Automated Automated Automated Automated Automated
The key idea: Security is not a phase — it's a practice. Every commit is linted, tested, scanned, and deployed automatically. Issues are caught in minutes, not weeks.
Git Workflow: Branching Strategy
For our fleet management system with 4-6 developers, we use GitHub Flow (simplified Git Flow):
main ─────────────────────────────────────────→ Production
│ ↑
├── feature/fleet-dashboard ─────────┤ (Pull Request + Review)
│ │
├── feature/nestjs-api ──────────────┤ (Pull Request + Review)
│ │
├── fix/gps-timeout ─────────────────┘ (Pull Request + Review)
Branch Naming Convention
feature/ → New features (feature/driver-management)
fix/ → Bug fixes (fix/gps-data-loss)
hotfix/ → Critical production (hotfix/auth-bypass)
chore/ → Maintenance (chore/update-dependencies)
docs/ → Documentation (docs/api-swagger)
Commit Convention (Conventional Commits)
feat(fleet): add vehicle tracking map component
fix(telemetry): resolve GPS data timeout on slow connections
docs(api): add swagger documentation for fleet endpoints
refactor(auth): extract JWT validation into guard
test(driver): add unit tests for license expiry check
chore(deps): update NestJS to v11
Why this matters: Automated release notes, clear git history, and easy to search. When a bug appears, you can quickly find which commit introduced it.
CI/CD Pipeline with GitHub Actions
Here's our complete pipeline:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
# ============================================
# Stage 1: Code Quality
# ============================================
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint # ESLint
- run: npm run type-check # TypeScript compiler check
- run: npm run format:check # Prettier check
# ============================================
# Stage 2: Tests
# ============================================
test:
runs-on: ubuntu-latest
needs: lint
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: fleet_test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
redis:
image: redis:7
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run test:unit # Unit tests
- run: npm run test:e2e # E2E tests
env:
DATABASE_URL: postgresql://test:test@localhost:5432/fleet_test
REDIS_URL: redis://localhost:6379
# ============================================
# Stage 3: Security Scanning
# ============================================
security:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
# Dependency vulnerability scanning
- run: npm audit --audit-level=high
# SAST (Static Application Security Testing)
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/typescript
p/nodejs
p/sql-injection
# ============================================
# Stage 4: Build & Deploy
# ============================================
deploy:
runs-on: ubuntu-latest
needs: [test, security]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t fleet-api:${{ github.sha }} .
- name: Deploy to production
run: |
ssh deploy@${{ secrets.SERVER_IP }} << 'EOF'
cd /var/www/fleet-api
git pull origin main
npm ci --production
npm run build
pm2 restart fleet-api
EOF
What Each Stage Does
| Stage | What It Checks | Time | Blocks Deploy? |
|---|---|---|---|
| Lint | Code style, TypeScript errors | ~30s | ✅ Yes |
| Test | Unit + E2E tests pass | ~2 min | ✅ Yes |
| Security | Vulnerability scanning | ~1 min | ✅ Yes |
| Deploy | Build + deploy to server | ~3 min | N/A |
Security Scanning in Detail
1. Dependency Audit
# Check for known vulnerabilities in dependencies
npm audit
# Fix automatically where possible
npm audit fix
# For production, fail on high/critical
npm audit --audit-level=high
Senior Tip: Run npm audit weekly, not just in CI. Dependencies get new vulnerabilities discovered all the time.
2. Static Application Security Testing (SAST)
SAST scans your source code for security issues without running it:
// Semgrep catches these patterns:
// ❌ SQL Injection vulnerability
const query = `SELECT * FROM users WHERE id = '${userId}'`;
// ✅ Safe — parameterized query
const query = 'SELECT * FROM users WHERE id = $1';
await pool.query(query, [userId]);
// ❌ XSS vulnerability
element.innerHTML = userInput;
// ✅ Safe — use textContent
element.textContent = userInput;
// ❌ Hardcoded secrets
const API_KEY = 'sk_live_abc123secret';
// ✅ Safe — use environment variables
const API_KEY = process.env.API_KEY;
3. Container Scanning
# Scan Docker images for OS-level vulnerabilities
- name: Scan Docker image
uses: aquasecurity/trivy-action@master
with:
image-ref: fleet-api:${{ github.sha }}
severity: 'CRITICAL,HIGH'
exit-code: '1' # Fail if critical issues found
Docker: Containerization
NestJS Dockerfile (Multi-stage Build)
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production (smaller image)
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --production && npm cache clean --force
COPY --from=builder /app/dist ./dist
# Security: Don't run as root
RUN addgroup -g 1001 appgroup && \
adduser -S -u 1001 -G appgroup appuser
USER appuser
EXPOSE 3001
CMD ["node", "dist/main.js"]
Why multi-stage? The build stage has devDependencies (300MB+). The production stage has only what's needed to run (~80MB). Smaller image = faster deploys + smaller attack surface.
Docker Compose for Local Development
# docker-compose.yml
version: '3.8'
services:
fleet-api:
build: ./apps/api
ports:
- "3001:3001"
environment:
- DATABASE_URL=postgresql://dev:dev@postgres:5432/fleet
- REDIS_URL=redis://redis:6379
depends_on:
- postgres
- redis
admin:
build: ./apps/admin
ports:
- "8000:8000"
environment:
- DB_HOST=mysql
- DB_DATABASE=fleet_admin
depends_on:
- mysql
postgres:
image: postgis/postgis:16-3.4
environment:
POSTGRES_DB: fleet
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
volumes:
- postgres_data:/var/lib/postgresql/data
mysql:
image: mysql:8
environment:
MYSQL_DATABASE: fleet_admin
MYSQL_ROOT_PASSWORD: dev
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
mysql_data:
Production Deployment
Nginx Reverse Proxy Configuration
# /etc/nginx/sites-available/fleet
server {
listen 80;
server_name fleet.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name fleet.example.com;
ssl_certificate /etc/letsencrypt/live/fleet.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/fleet.example.com/privkey.pem;
# Security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
location /api/ {
limit_req zone=api burst=50 nodelay;
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /admin/ {
proxy_pass http://127.0.0.1:8000;
# Only allow internal IPs
allow 10.0.0.0/8;
deny all;
}
}
Process Management with PM2
// ecosystem.config.js
module.exports = {
apps: [
{
name: 'fleet-api',
script: 'dist/main.js',
instances: 'max', // Use all CPU cores
exec_mode: 'cluster', // Cluster mode for load balancing
env: {
NODE_ENV: 'production',
PORT: 3001,
},
max_memory_restart: '500M',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
},
{
name: 'telemetry-service',
script: 'dist/main.js',
cwd: './apps/telemetry',
instances: 2,
env: {
NODE_ENV: 'production',
PORT: 3002,
},
},
],
};
Monitoring & Observability
Health Check Endpoint
// src/health/health.controller.ts
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
private redis: RedisHealthIndicator,
) {}
@Get()
async check() {
return this.health.check([
() => this.db.pingCheck('database'),
() => this.redis.pingCheck('redis'),
() => this.checkDiskSpace(),
() => this.checkMemoryUsage(),
]);
}
}
Structured Logging
// Use structured JSON logs — not console.log
this.logger.log({
event: 'delivery_created',
deliveryId: delivery.id,
vehicleId: delivery.vehicleId,
driverId: delivery.driverId,
timestamp: new Date().toISOString(),
});
// This makes logs searchable and filterable
// "Show me all delivery events for vehicle X in the last hour"
Incident Response Playbook
When something goes wrong in production, having a documented process prevents panic:
| Severity | Example | Response Time | Who |
|---|---|---|---|
| P0 - Critical | System down, no tracking | 15 minutes | On-call + Tech Lead |
| P1 - High | GPS data delayed > 5 min | 1 hour | On-call developer |
| P2 - Medium | Admin panel slow | 4 hours | Assigned developer |
| P3 - Low | UI bug, cosmetic issue | Next sprint | Any developer |
Post-Mortem Template
After every P0/P1 incident, write a post-mortem:
## Incident: GPS Data Loss (2026-05-15)
### Timeline
- 14:32 — Alert: Telemetry service stopped processing GPS data
- 14:35 — On-call acknowledged, began investigation
- 14:42 — Root cause found: Redis connection pool exhausted
- 14:45 — Fix applied: Increased connection pool from 10 to 50
- 14:48 — Service recovered, backlog cleared by 14:55
### Root Cause
Redis connection pool was set to 10 (default). Under peak load
(200 trucks × 6 updates/minute = 1200 ops/min), connections
were exhausted faster than they were released.
### Action Items
- [x] Increase Redis connection pool to 50
- [x] Add Redis connection pool monitoring to dashboard
- [ ] Add circuit breaker for Redis operations
- [ ] Load test with 2x expected traffic
Series Recap
Congratulations! Over 8 parts, we've built a complete enterprise Fleet Management System:
| Part | Topic | Key Skills Demonstrated |
|---|---|---|
| 1 | System Architecture | SDLC, architecture design, tech stack decisions |
| 2 | Next.js Dashboard | TypeScript, React, SSR, component architecture |
| 3 | NestJS Backend | NestJS, DI, DTOs, clean architecture |
| 4 | Laravel Admin | PHP, Laravel, Filament, event-driven architecture |
| 5 | Database Design | PostgreSQL, MySQL, Redis, polyglot persistence |
| 6 | SOLID Principles | Clean code, design patterns, code review |
| 7 | Microservices | Service boundaries, saga pattern, API gateway |
| 8 | DevSecOps | CI/CD, security scanning, Docker, deployment |
This isn't just theory — these are the patterns and practices I use daily as a senior full-stack developer. The ability to architect, build, secure, and deploy enterprise applications end-to-end is what separates senior developers from those who just write code.
This concludes the Fleet Management System series. If you found this helpful, check out my other series on Laravel E-Learning and React Native Event Management. Feel free to reach out with questions!

