Introduction
In this project, I built a mini production-style Incident Response Platform using Docker Compose.
The goal was not just to run one container. The goal was to model how real applications are often structured in DevOps and platform engineering environments: multiple services, clear separation of responsibilities, persistent storage, background processing, routing, health checks, and monitoring.
The final platform has seven services:
- Nginx edge reverse proxy
- Frontend web UI
- FastAPI backend API
- Background worker
- PostgreSQL database
- Redis cache and queue
- Prometheus monitoring
What Problem Does This Project Solve?
Every engineering team eventually deals with incidents:
- An API becomes slow
- A payment service fails
- A database starts timing out
- Error rates increase after a deployment
- Users report that an application is unavailable
In many teams, these issues are first tracked manually in chats, spreadsheets, or random notes. That works for very small teams, but it quickly becomes messy.
This project provides a simple incident management workflow:
- Create an incident
- Assign severity
- Assign ownership
- Acknowledge the incident
- Resolve the incident
- Store incident records
- Process background notifications
- Expose metrics for monitoring
It is intentionally small, but the architecture follows patterns used in real systems.
Project Architecture
The platform runs as a Docker Compose stack. A browser connects to Nginx, and Nginx routes requests either to the frontend or the API.
The API talks to PostgreSQL for persistent storage and Redis for caching and background jobs. A worker consumes jobs from Redis. Prometheus scrapes metrics from the API.
The high-level request flow looks like this:
Browser
-> Nginx edge service
-> Frontend web UI
-> FastAPI backend
-> PostgreSQL
-> Redis
-> Worker
-> Prometheus metrics endpoint
Project Folder Structure
I organized the project so each concern has its own place.
The important folders are:
incident-response-platform
|-- docker-compose.yml
|-- infra
| |-- nginx
| | `-- nginx.conf
| `-- prometheus
| `-- prometheus.yml
|-- services
| |-- api
| | |-- app
| | |-- Dockerfile
| | |-- requirements.txt
| | `-- worker.py
| `-- frontend
| |-- Dockerfile
| |-- index.html
| |-- app.js
| `-- styles.css
|-- .env.example
`-- README.md
This structure keeps infrastructure configuration separate from application code.
Defining the Seven Services in Docker Compose
The heart of the project is the docker-compose.yml file.
services:
edge:
image: nginx:1.27-alpine
container_name: incident-edge
depends_on:
- frontend
- api
ports:
- "8081:80"
volumes:
- ./infra/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
networks:
- incident-net
restart: unless-stopped
frontend:
build:
context: ./services/frontend
container_name: incident-frontend
expose:
- "80"
networks:
- incident-net
restart: unless-stopped
api:
build:
context: ./services/api
container_name: incident-api
env_file:
- .env
environment:
DATABASE_URL: postgresql://incident_app:incident_password@postgres:5432/incident_response
REDIS_URL: redis://redis:6379/0
ENVIRONMENT: local
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
expose:
- "8000"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()"]
interval: 15s
timeout: 5s
retries: 5
start_period: 20s
networks:
- incident-net
restart: unless-stopped
worker:
build:
context: ./services/api
container_name: incident-worker
command: ["python", "worker.py"]
env_file:
- .env
environment:
DATABASE_URL: postgresql://incident_app:incident_password@postgres:5432/incident_response
REDIS_URL: redis://redis:6379/0
ENVIRONMENT: local
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- incident-net
restart: unless-stopped
postgres:
image: postgres:16-alpine
container_name: incident-postgres
environment:
POSTGRES_DB: incident_response
POSTGRES_USER: incident_app
POSTGRES_PASSWORD: incident_password
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U incident_app -d incident_response"]
interval: 10s
timeout: 5s
retries: 5
networks:
- incident-net
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: incident-redis
command: ["redis-server", "--appendonly", "yes"]
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- incident-net
restart: unless-stopped
prometheus:
image: prom/prometheus:v2.55.1
container_name: incident-prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.enable-lifecycle"
ports:
- "9090:9090"
volumes:
- ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
depends_on:
- api
networks:
- incident-net
restart: unless-stopped
networks:
incident-net:
driver: bridge
volumes:
postgres-data:
redis-data:
prometheus-data:
This Compose file gives the application a production-style shape:
- Nginx is the only public application entry point
- The frontend and API run as separate services
- PostgreSQL and Redis have health checks
- Data is stored in named Docker volumes
- Services communicate over a private Docker network
- Prometheus collects metrics from the API
Running the Stack
To start the platform, I used:
docker compose up --build
This command builds the custom API, worker, and frontend images, pulls the required base images, creates the network, creates volumes, and starts all services.
To check the containers:
docker ps
The main URLs are:
Application: http://localhost:8081
API docs: http://localhost:8081/api/docs
Prometheus: http://localhost:9090
I used port 8081 because Jenkins was already running on port 8080.
The Web Application
Once the stack is running, the browser UI is available at:
http://localhost:8081
From the UI, an operator can:
- Create a new incident
- Set the affected service
- Choose the severity
- Assign an owner
- Acknowledge the incident
- Resolve the incident
When an incident is created, the frontend sends a request to the API through Nginx.
The API
The backend API is built with FastAPI.
FastAPI gives the project automatic Swagger documentation, which is useful for testing the API directly from the browser.
The API exposes endpoints like:
GET /health
GET /incidents
POST /incidents
PATCH /incidents/{incident_id}
GET /metrics
The /health endpoint is used by Docker health checks.
The /metrics endpoint exposes Prometheus-compatible metrics.
Background Jobs with Redis and Worker
When a new incident is created, the API does not do all the work inside the request.
Instead, it stores the incident in PostgreSQL and pushes a job into Redis.
The worker service listens for jobs from Redis and processes them asynchronously.
In this sample project, the worker simulates notifying the platform team. In a real-world system, this could be replaced with:
- Slack notifications
- Microsoft Teams notifications
- Email alerts
- PagerDuty escalation
- ServiceNow ticket creation
This pattern is important because web requests should stay fast. Slow or external tasks should usually run in the background.
Monitoring with Prometheus
Prometheus is included as the monitoring service.
It scrapes the API metrics endpoint:
http://api:8000/metrics
Some example metrics exposed by the API are:
incidents_created_total
incidents_resolved_total
open_incidents
Prometheus is available locally at:
http://localhost:9090
This gives the project a real observability layer instead of only relying on logs.
Publishing to GitHub
After building and testing the project locally, I initialized Git, committed the code, created a GitHub repository, and pushed the project.
The basic Git workflow was:
git init
git add .
git commit -m "Initial Docker Compose incident platform"
gh repo create incident-response-platform --private --source . --remote origin --push
If you want to share your own project publicly, create the repository as public or change the visibility later in GitHub settings.
DevOps Skills Practiced
This project helped me practice several real DevOps concepts:
- Docker Compose for multi-service applications
- Nginx as a reverse proxy
- FastAPI backend development
- PostgreSQL persistence
- Redis caching and background queues
- Background worker architecture
- Docker health checks
- Docker networks
- Docker volumes
- Prometheus metrics
- Git and GitHub workflow
- Production-style application thinking
Useful Commands
Start the project:
docker compose up --build
Run in detached mode:
docker compose up -d --build
Check running containers:
docker compose ps
View all logs:
docker compose logs -f
View only API logs:
docker compose logs -f api
View worker logs:
docker compose logs -f worker
Stop the stack:
docker compose down
Stop and remove volumes:
docker compose down -v
What I Would Add Next
This project is a strong foundation, but there are several ways to improve it:
- Add Grafana dashboards
- Add authentication
- Add real Slack or Teams notifications
- Add CI/CD with GitHub Actions
- Add container image scanning
- Add automated tests
- Add rate limiting in Nginx
- Add TLS for HTTPS
- Deploy it to a cloud environment
Conclusion
This project shows how Docker Compose can be used to build more than a simple container demo.
By combining Nginx, FastAPI, PostgreSQL, Redis, a worker, and Prometheus, I created a small but realistic incident response platform.
The biggest lesson is that DevOps is not only about running containers. It is about designing systems that are organized, observable, resilient, and easier to operate.
That is what this project demonstrates in a practical way.










Top comments (0)