CI/CD That Actually Works: From 4-Hour Deploys to 20 Minutes
- Ismail Kattakath
- DevOps , Engineering
- 08 Oct, 2025
When deployments take 4 hours, nobody deploys on Friday. Or Thursday. Or any day they don’t absolutely have to.
Slow deployments create fear. Fear creates batched releases. Batched releases create big, risky changes. It’s a death spiral for engineering velocity.
Here’s how we broke that cycle.
The Starting Point
The inherited pipeline:
- Manual build process with tribal knowledge
- No automated testing
- Deployment required SSH access and a runbook
- Average deployment: 4 hours
- Frequency: Every 2-3 weeks (when absolutely necessary)
The Target State
- Push to main triggers deployment
- Automated tests gate every merge
- Deployments complete in under 20 minutes
- Rollback takes 2 minutes
- Developers deploy multiple times per day
The Implementation
Step 1: Containerize Everything
Before automating deployment, we needed reproducible builds:
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
Same container runs in development, staging, and production. No more “works on my machine.”
Step 2: Automated Testing
Every pull request runs:
- Unit tests (Jest)
- Integration tests
- E2E tests (Playwright)
- Linting and type checking
Failed tests block the merge. No exceptions.
Step 3: GitHub Actions Pipeline
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
- run: npm run e2e
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push
run: |
docker build -t app:${{ github.sha }} .
docker push registry/app:${{ github.sha }}
- name: Deploy
run: kubectl set image deployment/app app=registry/app:${{ github.sha }}
Step 4: Feature Flags
Decoupled deployment from release:
- Deploy code anytime
- Enable features when ready
- Instant rollback via flag toggle
The Results
| Metric | Before | After |
|---|---|---|
| Deployment time | 4 hours | 18 minutes |
| Deployment frequency | Bi-weekly | Multiple daily |
| Failed deployments | ~20% | <2% |
| Time to rollback | 1 hour | 2 minutes |
Key Principles
- Automate the scary parts first — Manual steps are where errors happen
- Make deployments boring — If deployment is an event, you’re doing it wrong
- Test in production — With feature flags and monitoring, not YOLO
- Document everything — The pipeline is code, but context matters
Ready to fix your deployment process? Let’s talk.