Project Overview
This guide demonstrates how to deploy a Python web service using Docker with Nginx for load balencing. The example includes MySQL and Redis dependencies, along with instructions for disabling load balancing if not needed.
Project Structure
1. Dockerfile
# Base image for the application
FROM python:3.7
# Set working directory inside container
WORKDIR /app
# Copy application files to container
COPY . /app
# Install Python dependencies from requirements file
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
# Expose the application port
EXPOSE 1000
# Startup command
CMD ["sh", "entrypoint.sh"]
2. docker-compose.yml
version: '3.3'
networks:
app_network:
driver: bridge
services:
db_service:
image: mysql:5.7
container_name: db_service
environment:
MYSQL_DATABASE: 'appdb'
MYSQL_USER: 'dbuser'
MYSQL_PASSWORD: 'password123'
MYSQL_ROOT_PASSWORD: 'rootpass123'
volumes:
- ./data/mysql:/var/lib/mysql
ports:
- "3306:3306"
networks:
- app_network
cache_service:
image: redis:latest
container_name: cache_service
ports:
- "6379:6379"
volumes:
- ./data/redis:/data
networks:
- app_network
web_service:
build:
context: .
dockerfile: Dockerfile
container_name: web_service
image: web_image:v1.0
volumes:
- ./app:/app
depends_on:
- cache_service
- db_service
networks:
- app_network
web_service_secondary:
build:
context: .
dockerfile: Dockerfile
container_name: web_service_secondary
image: web_image:v1.0
volumes:
- ./app:/app
depends_on:
- cache_service
- db_service
networks:
- app_network
proxy_server:
image: nginx:latest
container_name: proxy_server
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- web_service
networks:
- app_network
3. Application Code (app.py)
import os
import socket
import logging
import redis
import pymysql
from flask import Flask, jsonify
from flask_cors import CORS
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__)
app.json.ensure_ascii = False
CORS(app, supports_credentials=True)
@app.route('/health', methods=['GET'])
def health_check():
"""Health check endpoint for load balancer."""
return jsonify({'status': 'healthy', 'service': 'web_app'})
@app.route('/api/data', methods=['GET'])
def get_data():
"""Main application endpoint demonstrating database and cache connectivity."""
# Initialize Redis connection using container hostname
try:
redis_client = redis.Redis(
host='cache_service',
port=6379,
db=0,
decode_responses=True
)
counter = redis_client.get('request_counter') or 0
redis_client.set('request_counter', int(counter) + 1)
cache_status = f"Request #{int(counter) + 1}"
except Exception as e:
logger.error(f"Redis connection failed: {e}")
cache_status = "Cache unavailable"
# Initialize MySQL connection using container hostname
try:
db_connection = pymysql.connect(
host='db_service',
user='root',
password='rootpass123',
database='appdb',
port=3306
)
with db_connection.cursor() as cursor:
cursor.execute('SELECT DATABASE()')
db_name = cursor.fetchone()[0]
db_status = f"Connected to {db_name}"
db_connection.close()
except Exception as e:
logger.error(f"Database connection failed: {e}")
db_status = f"Database error: {str(e)}"
# Get container metadata
container_name = socket.gethostname()
container_ip = socket.gethostbyname(container_name)
process_id = os.getpid()
return jsonify({
'code': 200,
'message': 'Service operational',
'cache': cache_status,
'database': db_status,
'metadata': {
'hostname': container_name,
'ip_address': container_ip,
'pid': process_id
}
})
if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0', port=1000)
4. Requirements File
flask==3.0.3
gunicorn==22.0.0
flask_cors==5.0.0
redis==5.2.0
pymysql==1.1.1
5. Startup Script (entrypoint.sh)
#!/bin/bash
# Start Gunicorn with multiple workers for handling concurrent requests
# --reload enables automatic reloading when code changes (useful in development)
# -w 4 spawns 4 worker processes
# --bind 0.0.0.0:1000 makes the service accessible from other containers
gunicorn --reload -w 4 --bind 0.0.0.0:1000 app:app
6. Nginx Configuration
worker_processes auto;
events {
worker_connections 1024;
}
http {
upstream backend {
least_conn;
server web_service:1000 weight=3;
server web_service_secondary:1000 weight=2;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /health {
return 200 'OK';
add_header Content-Type text/plain;
}
}
}
Deployment Instructions
Starting the Stack
# Start all containers in detached mode (background)
docker-compose up -d
# View container logs
docker-compose logs -f
# View status of all services
docker-compose ps
Troubleshooting
If image pull fails during startup, manually pull required images first:
docker pull mysql:5.7
docker pull redis:latest
docker pull nginx:latest
docker pull python:3.7
# Then start the stack
docker-compose up -d
To disable load balancing and run a single instance, comment out the web_service_secondary section and update the nginx upstream configuration to reference only one backend server.