60 Days DevOps Challenge: Day 9
Docker Networking, Volumes, and Compose
Initial Tasks:
✅ Task 1: Inspect the default Docker network and list all networks:
docker network ls
✅ Task 2: Install Docker Compose (Guide).
✅ Challenge 1: Create a custom bridge network and connect two containers (alpine and nginx).
Step 1: Create a Custom Bridge Network
docker network create my-custom-network
Step 2: Run an Nginx Container on the Custom Network
docker run -d --name nginx-container --network my-custom-network nginx
Step 3: Run an Alpine Container on the Same Network
docker run -it --rm --name alpine-container --network my-custom-network alpine sh
Step 4: Test Connectivity from Alpine to Nginx
apk add --no-cache curl
curl nginx-container
Step 5: Verify Network Connection
docker network ls
docker network inspect my-custom-network
✅ Challenge 2: Run a MySQL container with a named volume and confirm data persistence
Step 1: Create a Named Volume
docker volume create mysql_data
Step 2: Run MySQL with the Named Volume
docker run -d --name mysql-container \
-e MYSQL_ROOT_PASSWORD=rootpass \
-e MYSQL_DATABASE=mydb \
-v mysql_data:/var/lib/mysql \
-p 3306:3306 \
mysql:latest
Step 3: Verify Data Persistence
docker exec -it mysql-container mysql -uroot -prootpass -e "CREATE TABLE mydb.users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));"
docker exec -it mysql-container mysql -uroot -prootpass -e "INSERT INTO mydb.users (name) VALUES ('Alice'), ('Bob');"
docker exec -it mysql-container mysql -uroot -prootpass -e "SELECT * FROM mydb.users;"
Step 4: Restart and Verify
docker stop mysql-container
docker rm mysql-container
docker run -d --name mysql-container \
-e MYSQL_ROOT_PASSWORD=rootpass \
-e MYSQL_DATABASE=mydb \
-v mysql_data:/var/lib/mysql \
-p 3306:3306 \
mysql:latest
docker exec -it mysql-container mysql -uroot -prootpass -e "SELECT * FROM mydb.users;"
✅ Challenge 3: docker-compose.yml for Flask API and PostgreSQL
Step 1: app.py
from flask import Flask
import psycopg2
app = Flask(__name__)
@app.route("/")
def home():
return "🚀 Flask API with PostgreSQL is running!"
@app.route("/db-status")
def db_status():
try:
conn = psycopg2.connect(dbname="mydb", user="postgres", password="postgres", host="db")
conn.close()
return "✅ PostgreSQL is connected!"
except Exception as e:
return f"❌ Database connection failed: {e}"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Step 2: requirements.txt
flask
psycopg2
Step 3: Dockerfile
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
Step 4: docker-compose.yml
version: "3.8"
services:
flask-app:
build: .
container_name: flask-container
ports:
- "5000:5000"
depends_on:
- db
environment:
DATABASE_URL: "postgresql://postgres:postgres@db:5432/mydb"
db:
image: postgres:latest
container_name: postgres-container
environment:
POSTGRES_DB: mydb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Step 5: Run docker-compose
docker-compose up -d
Step 6: check the status and connection
curl http://localhost:5000/
curl http://localhost:5000/db-status
✅ Challenge 4: Secure credentials using environment variables
Step 1: .env
POSTGRES_DB=mydb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=supersecurepassword
Step 2: Modified docker-compose.yml
version: "3.8"
services:
flask-app:
build: .
container_name: flask-container
ports:
- "5000:5000"
depends_on:
- db
env_file:
- .env
environment:
DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
db:
image: postgres:latest
container_name: postgres-container
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
env_file:
- .env
volumes:
postgres_data:
Step 3: Updated app.py
import os
import psycopg2
from flask import Flask
app = Flask(__name__)
DB_NAME = os.getenv("POSTGRES_DB", "default_db")
DB_USER = os.getenv("POSTGRES_USER", "default_user")
DB_PASSWORD = os.getenv("POSTGRES_PASSWORD", "default_pass")
DB_HOST = "db"
@app.route("/")
def home():
return "🚀 Flask API with PostgreSQL using .env is running!"
@app.route("/db-status")
def db_status():
try:
conn = psycopg2.connect(dbname=DB_NAME, user=DB_USER, password=DB_PASSWORD, host=DB_HOST)
conn.close()
return "✅ PostgreSQL is connected!"
except Exception as e:
return f"❌ Database connection failed: {e}"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Step 4:
docker compose up -d --build
Step 5:
curl http://localhost:5000/
curl http://localhost:5000/db-status
docker exec -it flask-container env | grep POSTGRES
Bonus: Add .env to .gitignore
echo ".env" >> .gitignore
✅ Challenge 5: Deploy a WordPress site using Docker Compose (WordPress + MySQL)
Step 1: Create a .env File
# MySQL Database Credentials
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpressuser
MYSQL_PASSWORD=wordpresspass
# WordPress Configuration
WORDPRESS_DB_HOST=db
WORDPRESS_DB_USER=wordpressuser
WORDPRESS_DB_PASSWORD=wordpresspass
WORDPRESS_DB_NAME=wordpress
Step 2: Create the docker-compose.yml File
version: "3.8"
services:
wordpress:
image: wordpress:latest
container_name: wordpress-container
restart: always
depends_on:
- db
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: ${WORDPRESS_DB_HOST}
WORDPRESS_DB_USER: ${WORDPRESS_DB_USER}
WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD}
WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME}
env_file:
- .env
volumes:
- wordpress_data:/var/www/html
db:
image: mysql:latest
container_name: mysql-container
restart: always
command: --default-authentication-plugin=mysql_native_password
ports:
- "3306:3306"
env_file:
- .env
volumes:
- mysql_data:/var/lib/mysql
volumes:
wordpress_data:
mysql_data:
Step 3: Start WordPress + MySQL
docker-compose up -d
Step 4: Access WordPress
Open http://localhost:8080 in your browser.
Step 5: Verify Containers
docker ps
✅ Challenge 6: Reverse Proxy with Nginx for Flask
Step 1: app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "🚀 Flask API is running behind Nginx!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Step 2: requirements.txt
flask
Step 3: Dockerfile
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
Step 4: nginx.conf
server {
listen 80;
location / {
proxy_pass http://backend:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Step 5: docker-compose.yml
version: "3.8"
services:
nginx:
image: nginx:latest
container_name: nginx-proxy
ports:
- "8080:80"
depends_on:
- backend
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
restart: always
backend:
build: .
container_name: flask-backend
expose:
- "5000"
restart: always
Step 6: Build and Run
docker-compose up -d --build
Step 7: Test
curl http://localhost:8080
✅ Challenge 7: Network Alias for DB Access
Step 1: app.py
import os
import psycopg2
from flask import Flask
app = Flask(__name__)
DB_HOST = os.getenv("DB_HOST", "db-alias")
DB_NAME = os.getenv("POSTGRES_DB", "mydb")
DB_USER = os.getenv("POSTGRES_USER", "postgres")
DB_PASSWORD = os.getenv("POSTGRES_PASSWORD", "postgres")
@app.route("/")
def home():
return "🚀 Flask API connected to PostgreSQL using network alias!"
@app.route("/db-status")
def db_status():
try:
conn = psycopg2.connect(
dbname=DB_NAME, user=DB_USER,
password=DB_PASSWORD, host=DB_HOST
)
conn.close()
return "✅ PostgreSQL is connected via alias!"
except Exception as e:
return f"❌ Database connection failed: {e}"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Step 2: requirements.txt
flask
psycopg2
Step 3: Dockerfile
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
Step 4: .env
POSTGRES_DB=mydb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
DB_HOST=db-alias
Step 5: docker-compose.yml
version: "3.8"
services:
flask-app:
build: .
container_name: flask-container
ports:
- "5000:5000"
depends_on:
- db
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DB_HOST: db-alias
networks:
mynetwork:
aliases:
- app-alias
db:
image: postgres:latest
container_name: postgres-container
ports:
- "5432:5432"
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
mynetwork:
aliases:
- db-alias
volumes:
postgres_data:
networks:
mynetwork:
Step 6: Build and Run
docker-compose up -d --build
Step 7: Verify
curl http://localhost:5000/
curl http://localhost:5000/db-status
docker network inspect mynetwork
✅ Challenge 8: Inspect Container IP and Access via IP
Step 1: Run an Nginx Container
docker run -d --name my-nginx -p 8080:80 nginx
Step 2: Get Internal IP Address
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-nginx
Step 3: Access via Internal IP
curl http://<container_ip>
Example Output:
<!DOCTYPE html>
<html>
<head><title>Welcome to nginx!</title></head>
<body>
<h1>Welcome to nginx!</h1>
</body>
</html>
Step 4: Validate Network Info
docker network inspect bridge
✅ Challenge 9: Redis-Based Caching with Python (Flask)
Step 1: app.py
import os
import redis
from flask import Flask
app = Flask(__name__)
redis_host = os.getenv("REDIS_HOST", "redis")
redis_client = redis.Redis(host=redis_host, port=6379, db=0)
@app.route("/")
def home():
redis_client.incr("visits")
visits = redis_client.get("visits").decode("utf-8")
return f"🚀 Flask + Redis! Visit count: {visits}"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Step 2: requirements.txt
flask
redis
Step 3: Dockerfile
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
Step 4: docker-compose.yml
version: "3.8"
services:
flask-app:
build: .
container_name: flask-container
ports:
- "5000:5000"
depends_on:
- redis
environment:
REDIS_HOST: redis
restart: always
redis:
image: redis:latest
container_name: redis-cache
ports:
- "6379:6379"
restart: always
Step 5: Run the Setup
docker-compose up -d --build
Step 6: Test It
curl http://localhost:5000
Step 7: Tear Down
docker-compose down -v
✅ Challenge 10: Docker Compose Health Checks
Step 1: app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "🚀 Flask API is running!"
@app.route("/health")
def health():
return "✅ OK", 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Step 2: Dockerfile
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install flask
EXPOSE 5000
CMD ["python", "app.py"]
Step 3: docker-compose.yml with Health Checks
version: "3.8"
services:
flask-app:
build: .
container_name: flask-container
ports:
- "5000:5000"
restart: always
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 5s
redis:
image: redis:latest
container_name: redis-cache
ports:
- "6379:6379"
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
Step 4: Run with Health Check
docker-compose up -d --build
Step 5: Check Health
docker ps
Step 6: Inspect Detailed Status
docker inspect --format='{{json .State.Health}}' flask-container | jq
Bonus 💡 Restart Unhealthy Containers Automatically
Modify docker-compose.yml to restart unhealthy containers:
services:
flask-app:
build: .
container_name: flask-container
ports:
- "5000:5000"
restart: on-failure
depends_on:
redis:
condition: service_healthy # Wait for Redis to be healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 5s
redis:
image: redis:latest
container_name: redis-cache
ports:
- "6379:6379"
restart: on-failure
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
In conclusion, the 60 Days DevOps Challenge: Day 9 provided a comprehensive exploration of Docker networking, volumes, and Compose. Through a series of practical challenges, participants gained hands-on experience in creating custom networks, managing data persistence with volumes, and deploying applications using Docker Compose. The tasks also emphasized the importance of securing credentials, implementing reverse proxies, and utilizing health checks to ensure application reliability. By completing these challenges, participants have strengthened their understanding of Docker's capabilities and are better equipped to manage containerized applications in a DevOps environment.