DEV Community

Cover image for Building a 7-Service Docker Compose Incident Response Platform
Bernard Chika Uwaezuoke
Bernard Chika Uwaezuoke

Posted on

Building a 7-Service Docker Compose Incident Response Platform

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:

  1. Nginx edge reverse proxy
  2. Frontend web UI
  3. FastAPI backend API
  4. Background worker
  5. PostgreSQL database
  6. Redis cache and queue
  7. Prometheus monitoring

Cover image showing the 7-service Docker Compose incident response platform

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.

Architecture diagram for the incident response platform

The high-level request flow looks like this:

Browser
  -> Nginx edge service
    -> Frontend web UI
    -> FastAPI backend
      -> PostgreSQL
      -> Redis
        -> Worker
      -> Prometheus metrics endpoint
Enter fullscreen mode Exit fullscreen mode

Project Folder Structure

I organized the project so each concern has its own place.

Project folder structure in VS Code

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
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This command builds the custom API, worker, and frontend images, pulls the required base images, creates the network, creates volumes, and starts all services.

Docker Compose logs showing services starting and health checks passing

To check the containers:

docker ps
Enter fullscreen mode Exit fullscreen mode

docker ps output showing all seven containers running

The main URLs are:

Application: http://localhost:8081
API docs:    http://localhost:8081/api/docs
Prometheus:  http://localhost:9090
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Incident Response Platform web UI

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.

FastAPI Swagger documentation for the incident platform

The API exposes endpoints like:

GET    /health
GET    /incidents
POST   /incidents
PATCH  /incidents/{incident_id}
GET    /metrics
Enter fullscreen mode Exit fullscreen mode

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.

Runtime logs showing incident creation and worker processing

Monitoring with Prometheus

Prometheus is included as the monitoring service.

It scrapes the API metrics endpoint:

http://api:8000/metrics
Enter fullscreen mode Exit fullscreen mode

Some example metrics exposed by the API are:

incidents_created_total
incidents_resolved_total
open_incidents
Enter fullscreen mode Exit fullscreen mode

Prometheus is available locally at:

http://localhost:9090
Enter fullscreen mode Exit fullscreen mode

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.

GitHub repository screenshot

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
Enter fullscreen mode Exit fullscreen mode

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

Overview of DevOps skills learned from the project

Useful Commands

Start the project:

docker compose up --build
Enter fullscreen mode Exit fullscreen mode

Run in detached mode:

docker compose up -d --build
Enter fullscreen mode Exit fullscreen mode

Check running containers:

docker compose ps
Enter fullscreen mode Exit fullscreen mode

View all logs:

docker compose logs -f
Enter fullscreen mode Exit fullscreen mode

View only API logs:

docker compose logs -f api
Enter fullscreen mode Exit fullscreen mode

View worker logs:

docker compose logs -f worker
Enter fullscreen mode Exit fullscreen mode

Stop the stack:

docker compose down
Enter fullscreen mode Exit fullscreen mode

Stop and remove volumes:

docker compose down -v
Enter fullscreen mode Exit fullscreen mode

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)