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


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).
Subscribe to my newsletter
Read articles from Usman Jap directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
