Day 10: Modularizing My Flask App with Docker Compose Profiles and Overrides – A Fun DevOps Adventure!

Usman JapUsman Jap
5 min read

Now, it’s time to level up with Docker Compose profiles and overrides, making my Flask + Redis app modular and environment-ready. Plus, I had to squash a pesky bug along the way. Grab a coffee, and let’s dive into this 2-hour, hands-on, fun-filled adventure from May 30, 2025!

Why Day 10 Rocks

Today’s mission was to make my Flask + Redis app more flexible using Docker Compose profiles and overrides. Think of profiles as a way to say, “Hey, Docker, only run these services for dev or prod!” and overrides as a magic wand to tweak settings without touching the main recipe. I also practiced commands like docker compose --profile, used Grok to dig into modularity.

1. Hands-On: Making My Flask App Modular with Profiles and Overrides (60 Minutes)

Kicking Off with a Quick Review

I started my session at 10:00 AM WIB, buzzing with excitement. First, I used spaced repetition to revisit Days 1–9. I skimmed my Day_1_Activities.txt to Day_9_Activities.txt and tested myself with active recall:

  • Why did the /health endpoint fix my 404 errors? (It checks Redis connectivity.)

  • How did .env stop interpolation errors? (It fed variables like REDIS_HOST to Compose.)

  • What’s the deal with docker compose logs? (It shows container logs for debugging.)

To set the stage, I asked Grok (via grok.com’s free tier): “Explain Docker Compose profiles and overrides in 50 words.” Grok replied: “Profiles selectively activate services (e.g., dev or prod) using --profile. Overrides, like docker-compose.dev.yml, customize settings without altering the base YAML, enabling environment-specific tweaks like debug modes or resource limits. Together, they make configurations modular and reusable.” Perfect intro—Grok’s my trusty sidekick!

Adding Profiles for Dev and Prod

I navigated to my project folder:

bash

cd ~/tmp/flask-redis-app

Then, I opened docker-compose.yml with nano and added profiles to my Flask + Redis app. Here’s the updated YAML (simplified for the blog):

yaml

services:
  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      - REDIS_HOST=${REDIS_HOST}
      - APP_TITLE=${APP_TITLE}
    secrets:
      - redis_password
    depends_on:
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    profiles:
      - dev
    networks:
      - flask-net
  redis:
    image: redis:latest
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data
    profiles:
      - dev
      - prod
    networks:
      - flask-net
  web-prod:
    build:
      context: .
      dockerfile: Dockerfile.prod
    ports:
      - "8080:5000"
    environment:
      - REDIS_HOST=${REDIS_HOST}
      - APP_TITLE=${APP_TITLE_PROD}
    profiles:
      - prod
    networks:
      - flask-net
networks:
  flask-net:
    driver: bridge
volumes:
  redis-data:
secrets:
  redis_password:
    file: ./redis_password.txt

What’s new? I added profiles: [dev] to web, profiles: [dev, prod] to redis, and a new web-prod service with profiles: [prod] for production, running on port 8080 with stricter health checks. I also dropped version: '3.8' per Day 9’s fix for compatibility.

Next, I created a production-grade Dockerfile.prod:

bash

nano Dockerfile.prod

dockerfile

FROM python:3.9-slim
WORKDIR /app
COPY app/ .
RUN pip install flask redis gunicorn
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]

This uses gunicorn for a robust WSGI server—prod vibes only! I updated my .env file:

bash

nano .env

Added:

APP_TITLE_PROD=Production Flask App

The Bug Fight: Squashing a NameError

When I ran docker compose --profile dev up -d --build, disaster struck! The web container crashed with:

bash

docker logs flask-redis-app-web-1

Output:

Traceback (most recent call last):
  File "/app/app.py", line 12, in <module>
    redis = Redis(host=redis_host, port=6379, password=open('/run/secrets/redis_password').read().strip(), decode_responses=True)
NameError: name 'Redis' is not defined

Oof! The Redis class wasn’t imported. I fixed app.py:

bash

nano app/app.py

Updated to:

python

from flask import Flask
from redis import Redis
import os

app = Flask(__name__)
redis_host = os.getenv('REDIS_HOST', 'redis')
redis = Redis(host=redis_host, port=6379, password=open('/run/secrets/redis_password').read().strip(), decode_responses=True)

@app.route('/')
def index():
    count = redis.incr('visits')
    app_title = os.getenv('APP_TITLE', 'Default Flask App')
    return f"{app_title}: Visited {count} times."

@app.route('/health')
def health():
    if redis.ping():
        return 'OK'
    return 'Redis Unavailable', 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

The fix? Added from redis import Redis. I rebuilt and ran:

bash

docker compose down
docker compose --profile dev up -d --build

Checked:

bash

docker ps -a

Both web and redis were Up (healthy)! Tested endpoints:

bash

curl http://localhost:5000

Output: “App from .env: Visited 1 times.”

bash

curl http://localhost:5000/health

Output: “OK”. Success!

Adding an Override for Dev

I created docker-compose.dev.yml for dev-specific tweaks:

bash

nano docker-compose.dev.yml

yaml

services:
  web:
    environment:
      - FLASK_ENV=development
    volumes:
      - ./app:/app

This enables debug mode and live code reloading. I tested it:

bash

docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile dev up -d

Edited app.py to add print("Debug mode"), then checked logs:

bash

docker compose logs web

Saw my debug message—live reloading worked! I also tested the prod profile:

bash

docker compose down
docker compose --profile prod up -d --build
curl http://localhost:8080

Output: “Production Flask App: Visited 2 times.” Modular magic!

Wrapping Up

I jotted a 50-word summary in day_10_notes.txt: “Profiles enable environment-specific services (dev, prod), while overrides like docker-compose.dev.yml customize settings without altering the base YAML. This modularity simplifies Flask app deployment, supports live code changes, and aligns with 2025 DevOps trends for scalable, maintainable configs.”


2. Mastering Commands and Grok Queries (45 Minutes)

At 11:00 AM, I dove into deliberate practice with Compose commands. From ~/tmp/flask-redis-app, I ran:

bash

docker compose --profile dev config

This showed the dev profile’s services. I started just Redis:

bash

docker compose --profile dev up -d redis

Checked active services:

bash

docker compose config --services

Output: web, redis for dev; web-prod, redis for prod.

I turned to Grok for deeper insights, asking: “Explain Docker Compose profiles vs. overrides in 100 words.” Grok’s response (paraphrased): “Profiles selectively activate services (e.g., --profile dev runs dev services). Overrides, via files like docker-compose.dev.yml, tweak settings like ports or volumes. Profiles control what runs; overrides customize how it runs, enabling modular, environment-specific setups without changing the base YAML.”

Using DeepSearch, I queried: “Find X posts on Docker Compose profiles in 2025.” I found a post from @docker: “Profiles in Compose simplify multi-env setups—dev, test, prod in one YAML!” I summarized it in day_10_notes.txt. In think mode, I asked: “Step-by-step, how does Docker Compose process profiles and overrides?” Grok explained: “Compose merges base and override files, then filters services by active profiles, ensuring only relevant services run with customized settings.”

For a mini-project, I created docker-compose.prod.yml:

yaml

services:
  web-prod:
    environment:
      - FLASK_ENV=production
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M

Ran it:

bash

docker compose -f docker-compose.yml -f docker-compose.prod.yml --profile prod up -d
curl http://localhost:8080

It worked like a charm! I noted three commands in day_10_notes.txt: --profile (selects profile), config --services (lists services), up (starts services).


0
Subscribe to my newsletter

Read articles from Usman Jap directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Usman Jap
Usman Jap