<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Osayuki Omo</title>
    <description>The latest articles on DEV Community by Osayuki Omo (@the_man_yuki).</description>
    <link>https://dev.to/the_man_yuki</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3778439%2F718d3954-6b85-44b5-9399-14d92cb62d1f.png</url>
      <title>DEV Community: Osayuki Omo</title>
      <link>https://dev.to/the_man_yuki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/the_man_yuki"/>
    <language>en</language>
    <item>
      <title>How I Built a Fully Containerized Flask Application with Docker, PostgreSQL, Redis &amp; Monitoring</title>
      <dc:creator>Osayuki Omo</dc:creator>
      <pubDate>Tue, 17 Feb 2026 23:53:29 +0000</pubDate>
      <link>https://dev.to/the_man_yuki/how-i-built-a-fully-containerized-flask-application-with-docker-postgresql-redis-monitoring-2n01</link>
      <guid>https://dev.to/the_man_yuki/how-i-built-a-fully-containerized-flask-application-with-docker-postgresql-redis-monitoring-2n01</guid>
      <description>&lt;p&gt;When we use apps like Instagram, Uber, or Netflix, we don’t think about what’s happening behind the scenes.&lt;/p&gt;

&lt;p&gt;But behind every app, there’s:&lt;/p&gt;

&lt;p&gt;A server handling requests&lt;/p&gt;

&lt;p&gt;A database storing information&lt;/p&gt;

&lt;p&gt;A system that checks if everything is working&lt;/p&gt;

&lt;p&gt;Monitoring tools watching performance&lt;/p&gt;

&lt;p&gt;In this project, I built a small version of that real-world system using:&lt;/p&gt;

&lt;p&gt;Flask (a Python web framework)&lt;/p&gt;

&lt;p&gt;PostgreSQL (a database)&lt;/p&gt;

&lt;p&gt;Redis (a cache)&lt;/p&gt;

&lt;p&gt;Docker (to run everything in containers)&lt;/p&gt;

&lt;p&gt;Prometheus (to collect metrics)&lt;/p&gt;

&lt;p&gt;Grafana (to visualize those metrics)&lt;/p&gt;

&lt;p&gt;Let me explain it in simple words.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 What This Project Does (In Simple Terms)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine this scenario:&lt;/p&gt;

&lt;p&gt;You open an app and request some data.&lt;/p&gt;

&lt;p&gt;Here’s what happens in my project:&lt;/p&gt;

&lt;p&gt;You send a request to the Flask API.&lt;/p&gt;

&lt;p&gt;The API checks Redis first (fast memory).&lt;/p&gt;

&lt;p&gt;If data isn’t there, it asks PostgreSQL (database).&lt;/p&gt;

&lt;p&gt;The API sends the response back to you.&lt;/p&gt;

&lt;p&gt;Meanwhile, Prometheus tracks how many requests were made.&lt;/p&gt;

&lt;p&gt;Grafana displays those numbers in charts.&lt;/p&gt;

&lt;p&gt;So this project is basically:&lt;/p&gt;

&lt;p&gt;👉 A small backend system that works like a real production server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🏗️ Architecture Overview (Simple Illustration)&lt;/strong&gt;&lt;br&gt;
4&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4d23e0nngfvsuik271sh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4d23e0nngfvsuik271sh.png" alt=" " width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5txv1qaign0x0w46c2t3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5txv1qaign0x0w46c2t3.png" alt=" " width="800" height="306"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkh3akmprd6i65oupxxby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkh3akmprd6i65oupxxby.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyi410ghio403d0drkkg4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyi410ghio403d0drkkg4.jpg" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s imagine it like a restaurant:&lt;/p&gt;

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

&lt;p&gt;Each service has its own “room” (container), but they all work together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🐍 The Core Flask App (The Heart of Everything)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s a simplified version of the main app:&lt;/p&gt;

&lt;p&gt;from flask import Flask&lt;br&gt;
from prometheus_client import Counter, generate_latest&lt;/p&gt;

&lt;p&gt;app = Flask(&lt;strong&gt;name&lt;/strong&gt;)&lt;/p&gt;

&lt;h1&gt;
  
  
  Create a counter to track requests
&lt;/h1&gt;

&lt;p&gt;REQUEST_COUNT = Counter('app_requests_total', 'Total number of requests')&lt;/p&gt;

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

&lt;p&gt;@app.route('/metrics')&lt;br&gt;
def metrics():&lt;br&gt;
    return generate_latest()&lt;/p&gt;

&lt;p&gt;What’s happening here?&lt;/p&gt;

&lt;p&gt;Flask() creates our web server.&lt;/p&gt;

&lt;p&gt;Counter() creates a metric that counts visits.&lt;/p&gt;

&lt;p&gt;Every time someone visits /, we increase the counter.&lt;/p&gt;

&lt;p&gt;/metrics lets Prometheus read those numbers.&lt;/p&gt;

&lt;p&gt;Think of it like putting a click counter on a website.&lt;/p&gt;

&lt;p&gt;Every visit = +1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🐳 What is Docker and Why Did I Use It?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before Docker, setting up apps could be messy:&lt;/p&gt;

&lt;p&gt;“It works on my computer but not yours.”&lt;br&gt;
Different software versions.&lt;br&gt;
Dependency conflicts.&lt;/p&gt;

&lt;p&gt;Docker solves that by putting each service into its own container.&lt;/p&gt;

&lt;p&gt;Think of a container like:&lt;/p&gt;

&lt;p&gt;📦 A lunchbox with everything the app needs inside.&lt;/p&gt;

&lt;p&gt;No matter where you run it — it behaves the same.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;📄 Dockerfile Explained *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here’s the Dockerfile:&lt;/p&gt;

&lt;p&gt;FROM python:3.10&lt;/p&gt;

&lt;p&gt;WORKDIR /app&lt;/p&gt;

&lt;p&gt;COPY requirements.txt .&lt;br&gt;
RUN pip install -r requirements.txt&lt;/p&gt;

&lt;p&gt;COPY . .&lt;/p&gt;

&lt;p&gt;CMD ["python", "app.py"]&lt;/p&gt;

&lt;p&gt;This is like a recipe:&lt;/p&gt;

&lt;p&gt;FROM python:3.10 → Start with a kitchen that already has Python.&lt;/p&gt;

&lt;p&gt;WORKDIR /app → Work inside the “app” folder.&lt;/p&gt;

&lt;p&gt;COPY requirements.txt . → Bring the list of ingredients.&lt;/p&gt;

&lt;p&gt;RUN pip install → Install dependencies.&lt;/p&gt;

&lt;p&gt;CMD → Start the app.&lt;/p&gt;

&lt;p&gt;Without this file, Docker wouldn’t know how to build the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧩 Docker Compose Explained (The Big Organizer)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If Dockerfile builds one container,&lt;br&gt;
Docker Compose runs many containers together.&lt;/p&gt;

&lt;p&gt;Here’s a simplified version:&lt;/p&gt;

&lt;p&gt;version: "3.9"&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  web:&lt;br&gt;
    build: .&lt;br&gt;
    ports:&lt;br&gt;
      - "5000:5000"&lt;/p&gt;

&lt;p&gt;postgres:&lt;br&gt;
    image: postgres:15&lt;br&gt;
    volumes:&lt;br&gt;
      - pgdata:/var/lib/postgresql/data&lt;/p&gt;

&lt;p&gt;redis:&lt;br&gt;
    image: redis:7&lt;/p&gt;

&lt;p&gt;prometheus:&lt;br&gt;
    image: prom/prometheus&lt;br&gt;
    ports:&lt;br&gt;
      - "9090:9090"&lt;/p&gt;

&lt;p&gt;grafana:&lt;br&gt;
    image: grafana/grafana&lt;br&gt;
    ports:&lt;br&gt;
      - "3000:3000"&lt;/p&gt;

&lt;p&gt;volumes:&lt;br&gt;
  pgdata:&lt;/p&gt;

&lt;p&gt;This file says:&lt;/p&gt;

&lt;p&gt;Build the Flask app&lt;/p&gt;

&lt;p&gt;Run PostgreSQL&lt;/p&gt;

&lt;p&gt;Run Redis&lt;/p&gt;

&lt;p&gt;Run Prometheus&lt;/p&gt;

&lt;p&gt;Run Grafana&lt;/p&gt;

&lt;p&gt;Save database data permanently&lt;/p&gt;

&lt;p&gt;And start everything with:&lt;/p&gt;

&lt;p&gt;docker compose up -d&lt;/p&gt;

&lt;p&gt;One command.&lt;br&gt;
Five services.&lt;br&gt;
Fully running system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💾 Volumes (Why They Matter)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without volumes:&lt;/p&gt;

&lt;p&gt;If the container stops, your database data disappears.&lt;/p&gt;

&lt;p&gt;With volumes:&lt;/p&gt;

&lt;p&gt;Data stays safe.&lt;/p&gt;

&lt;p&gt;In the compose file:&lt;/p&gt;

&lt;p&gt;volumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pgdata:/var/lib/postgresql/data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;p&gt;“Store database files outside the container.”&lt;/p&gt;

&lt;p&gt;Volumes are like external hard drives attached to containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🌐 Networking (How Containers Talk)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At first, Grafana couldn’t connect to Prometheus.&lt;/p&gt;

&lt;p&gt;It kept timing out.&lt;/p&gt;

&lt;p&gt;The mistake?&lt;/p&gt;

&lt;p&gt;I used:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:9090" rel="noopener noreferrer"&gt;http://localhost:9090&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside Docker, localhost means:&lt;/p&gt;

&lt;p&gt;“The container itself.”&lt;/p&gt;

&lt;p&gt;The correct way was:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://prometheus:9090" rel="noopener noreferrer"&gt;http://prometheus:9090&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because Docker automatically creates a private network.&lt;/p&gt;

&lt;p&gt;Containers talk using service names.&lt;/p&gt;

&lt;p&gt;It’s like calling someone using their office extension number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📊 Monitoring (Why This Is Important)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most beginner projects stop at:&lt;/p&gt;

&lt;p&gt;“It works!”&lt;/p&gt;

&lt;p&gt;But companies need to know:&lt;/p&gt;

&lt;p&gt;How many users are hitting the API?&lt;/p&gt;

&lt;p&gt;Is the server slow?&lt;/p&gt;

&lt;p&gt;Is it down?&lt;/p&gt;

&lt;p&gt;That’s where monitoring comes in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📈 Prometheus&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prometheus collects numbers (metrics).&lt;/p&gt;

&lt;p&gt;Example metrics:&lt;/p&gt;

&lt;p&gt;Total requests&lt;/p&gt;

&lt;p&gt;Requests per second&lt;/p&gt;

&lt;p&gt;Service status&lt;/p&gt;

&lt;p&gt;Prometheus configuration:&lt;/p&gt;

&lt;p&gt;scrape_configs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;job_name: 'flask_app'
static_configs:

&lt;ul&gt;
&lt;li&gt;targets: ['web:5000']&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This tells Prometheus:&lt;/p&gt;

&lt;p&gt;“Go check the Flask app for metrics.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📊 Grafana&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Grafana turns those numbers into beautiful graphs.&lt;/p&gt;

&lt;p&gt;When I added this query:&lt;/p&gt;

&lt;p&gt;app_requests_total&lt;/p&gt;

&lt;p&gt;I could literally see the graph go up as I refreshed the page.&lt;/p&gt;

&lt;p&gt;That moment changed everything.&lt;/p&gt;

&lt;p&gt;I wasn’t just coding.&lt;br&gt;
I was observing a live system.&lt;/p&gt;

&lt;p&gt;⚠️ Challenges I Faced&lt;br&gt;
1️⃣ Grafana Timeout Errors&lt;/p&gt;

&lt;p&gt;Problem:&lt;br&gt;
It wouldn’t connect to Prometheus.&lt;/p&gt;

&lt;p&gt;Cause:&lt;br&gt;
Wrong URL.&lt;/p&gt;

&lt;p&gt;Lesson:&lt;br&gt;
Containers talk using service names like:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://prometheus:9090" rel="noopener noreferrer"&gt;http://prometheus:9090&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not localhost.&lt;/p&gt;

&lt;p&gt;2️⃣ “Unhealthy” Flask Container&lt;/p&gt;

&lt;p&gt;Problem:&lt;br&gt;
Docker marked my app as unhealthy.&lt;/p&gt;

&lt;p&gt;Cause:&lt;br&gt;
Health check didn’t match real route.&lt;/p&gt;

&lt;p&gt;Example health check:&lt;/p&gt;

&lt;p&gt;healthcheck:&lt;br&gt;
  test: ["CMD", "curl", "-f", "&lt;a href="http://localhost:5000/%22" rel="noopener noreferrer"&gt;http://localhost:5000/"&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Lesson:&lt;br&gt;
Health checks must point to real working endpoints.&lt;/p&gt;

&lt;p&gt;Monitoring is about reliability, not just graphs.&lt;/p&gt;

&lt;p&gt;3️⃣ Data Disappearing&lt;/p&gt;

&lt;p&gt;Without volumes, data resets when containers restart.&lt;/p&gt;

&lt;p&gt;That was a big lesson:&lt;/p&gt;

&lt;p&gt;Containers are temporary.&lt;br&gt;
Data must be persistent.&lt;/p&gt;

&lt;p&gt;This is the link to the project on github: &lt;a href="https://github.com/ManYuki/Project.git" rel="noopener noreferrer"&gt;https://github.com/ManYuki/Project.git&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>monitoring</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
