DEV Community

Osayuki Omo
Osayuki Omo

Posted on

How I Built a Fully Containerized Flask Application with Docker, PostgreSQL, Redis & Monitoring

When we use apps like Instagram, Uber, or Netflix, we don’t think about what’s happening behind the scenes.

But behind every app, there’s:

A server handling requests

A database storing information

A system that checks if everything is working

Monitoring tools watching performance

In this project, I built a small version of that real-world system using:

Flask (a Python web framework)

PostgreSQL (a database)

Redis (a cache)

Docker (to run everything in containers)

Prometheus (to collect metrics)

Grafana (to visualize those metrics)

Let me explain it in simple words.

💡 What This Project Does (In Simple Terms)

Imagine this scenario:

You open an app and request some data.

Here’s what happens in my project:

You send a request to the Flask API.

The API checks Redis first (fast memory).

If data isn’t there, it asks PostgreSQL (database).

The API sends the response back to you.

Meanwhile, Prometheus tracks how many requests were made.

Grafana displays those numbers in charts.

So this project is basically:

👉 A small backend system that works like a real production server.

🏗️ Architecture Overview (Simple Illustration)
4



Let’s imagine it like a restaurant:

👤 Customer → Sends request
👨‍🍳 Flask API → Chef preparing the food
🧠 Redis → Quick memory of popular orders
📚 PostgreSQL → Storage room with all ingredients
📊 Prometheus → Manager counting how many customers came
📈 Grafana → Dashboard showing daily performance

Each service has its own “room” (container), but they all work together.

🐍 The Core Flask App (The Heart of Everything)

Here’s a simplified version of the main app:

from flask import Flask
from prometheus_client import Counter, generate_latest

app = Flask(name)

Create a counter to track requests

REQUEST_COUNT = Counter('app_requests_total', 'Total number of requests')

@app.route('/')
def home():
REQUEST_COUNT.inc() # Increase counter every time someone visits
return "Hello, World!"

@app.route('/metrics')
def metrics():
return generate_latest()

What’s happening here?

Flask() creates our web server.

Counter() creates a metric that counts visits.

Every time someone visits /, we increase the counter.

/metrics lets Prometheus read those numbers.

Think of it like putting a click counter on a website.

Every visit = +1.

🐳 What is Docker and Why Did I Use It?

Before Docker, setting up apps could be messy:

“It works on my computer but not yours.”
Different software versions.
Dependency conflicts.

Docker solves that by putting each service into its own container.

Think of a container like:

📦 A lunchbox with everything the app needs inside.

No matter where you run it — it behaves the same.

*📄 Dockerfile Explained *

Here’s the Dockerfile:

FROM python:3.10

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

CMD ["python", "app.py"]

This is like a recipe:

FROM python:3.10 → Start with a kitchen that already has Python.

WORKDIR /app → Work inside the “app” folder.

COPY requirements.txt . → Bring the list of ingredients.

RUN pip install → Install dependencies.

CMD → Start the app.

Without this file, Docker wouldn’t know how to build the app.

🧩 Docker Compose Explained (The Big Organizer)

If Dockerfile builds one container,
Docker Compose runs many containers together.

Here’s a simplified version:

version: "3.9"

services:
web:
build: .
ports:
- "5000:5000"

postgres:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data

redis:
image: redis:7

prometheus:
image: prom/prometheus
ports:
- "9090:9090"

grafana:
image: grafana/grafana
ports:
- "3000:3000"

volumes:
pgdata:

This file says:

Build the Flask app

Run PostgreSQL

Run Redis

Run Prometheus

Run Grafana

Save database data permanently

And start everything with:

docker compose up -d

One command.
Five services.
Fully running system.

💾 Volumes (Why They Matter)

Without volumes:

If the container stops, your database data disappears.

With volumes:

Data stays safe.

In the compose file:

volumes:

  • pgdata:/var/lib/postgresql/data

This means:

“Store database files outside the container.”

Volumes are like external hard drives attached to containers.

🌐 Networking (How Containers Talk)

At first, Grafana couldn’t connect to Prometheus.

It kept timing out.

The mistake?

I used:

http://localhost:9090

Inside Docker, localhost means:

“The container itself.”

The correct way was:

http://prometheus:9090

Because Docker automatically creates a private network.

Containers talk using service names.

It’s like calling someone using their office extension number.

📊 Monitoring (Why This Is Important)

Most beginner projects stop at:

“It works!”

But companies need to know:

How many users are hitting the API?

Is the server slow?

Is it down?

That’s where monitoring comes in.

📈 Prometheus

Prometheus collects numbers (metrics).

Example metrics:

Total requests

Requests per second

Service status

Prometheus configuration:

scrape_configs:

  • job_name: 'flask_app' static_configs:
    • targets: ['web:5000']

This tells Prometheus:

“Go check the Flask app for metrics.”

📊 Grafana

Grafana turns those numbers into beautiful graphs.

When I added this query:

app_requests_total

I could literally see the graph go up as I refreshed the page.

That moment changed everything.

I wasn’t just coding.
I was observing a live system.

⚠️ Challenges I Faced
1️⃣ Grafana Timeout Errors

Problem:
It wouldn’t connect to Prometheus.

Cause:
Wrong URL.

Lesson:
Containers talk using service names like:

http://prometheus:9090

Not localhost.

2️⃣ “Unhealthy” Flask Container

Problem:
Docker marked my app as unhealthy.

Cause:
Health check didn’t match real route.

Example health check:

healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/"]

Lesson:
Health checks must point to real working endpoints.

Monitoring is about reliability, not just graphs.

3️⃣ Data Disappearing

Without volumes, data resets when containers restart.

That was a big lesson:

Containers are temporary.
Data must be persistent.

This is the link to the project on github: https://github.com/ManYuki/Project.git

Top comments (0)