The Challenge
I was given a multi-service application with:
A Node.js frontend
A FastAPI backend
A Python worker
Redis as a queue
The catch?
It was intentionally broken.
My task wasn’t to build it was to:
- Fix it
- Containerize it
- Ship it with a full CI/CD pipeline
Step 1: Debugging the System
Before touching Docker, I read the entire codebase.
Issues I found:
Hardcoded localhost breaking container networking
Missing environment variables
Redis connection failures
API crashing on startup
Frontend calling wrong endpoints
Fix approach:
Replaced hardcoded values with env variables
Standardized service communication (api, redis)
Added proper error handling
Step 2: Containerization
Each service got its own Dockerfile.
Key decisions:
- Used python:3.11-alpine for minimal size
- Created non-root users (appuser)
- Added HEALTHCHECK to every service
- Avoided copying .env files into images
Step 3: Docker Compose Setup
This was where things got interesting.
What I implemented:
- Internal bridge network
- Redis hidden from host
- depends_on with health conditions
- Resource limits for all containers
depends_on:
redis:
condition: service_healthy
Step 4: CI/CD Pipeline
I built a GitHub Actions pipeline with strict stages:
- Lint
flake8 (Python)
eslint (Node)
hadolint (Docker)
Test
pytest with mocked Redis
coverage report upload
- Build
Tagged images with SHA + latest
Pushed to local registry
- Security Scan
Trivy scan
Fail on CRITICAL vulnerabilities
- Integration Test
Spin up full stack
Submit job → poll → verify completion
- Deploy
Rolling update
Health check gating
Automatic rollback on failure
Challenges I Faced
- Docker daemon not running (blocked everything)
- ESLint breaking due to Node version mismatch
- Trivy failing due to missing images
- Race conditions between services
- Containers “starting” but not “ready”
Key Takeaways
- Health checks > startup order
- Containers must be self-sufficient
- CI/CD pipelines should fail fast DevOps is about system thinking, not just tools



Top comments (0)