Dockerfile and Docker Compose
Contents
- Dockerfile Basics with Python
- Multi-Stage Python Builds
- Docker Compose for Python Development
- Python Database Integration
- Development vs Production Configurations
- Containerized Python Testing
- Python Microservices Architecture
- Environment Management
- Health Checks and Monitoring
- Container Resource Management
- Network Configuration and Service Discovery
- Security Best Practices
- Real-world Application: ML Model Deployment
- Results for ML Model Deployment
- Real-world Application: Distributed Task Processing
- Additional Resources
Dockerfile Basics with Python
A Dockerfile is a text document containing instructions to build a Docker image automatically. For Python applications, it defines the base image, working directory, dependency installations, and commands to run the application.
# Example Python application
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello from Dockerized Flask!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
# Corresponding Dockerfile
'''
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
'''
Multi-Stage Python Builds
Multi-stage builds allow creating optimized Docker images by separating build dependencies from runtime dependencies, significantly reducing the final image size while maintaining functionality and security.
'''
# Multi-stage Dockerfile
FROM python:3.9 AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --user -r requirements.txt
FROM python:3.9-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
'''
# Example requirements.txt
'''
numpy==1.21.0
pandas==1.3.0
scikit-learn==0.24.2
'''
Docker Compose for Python Development
Docker Compose orchestrates multi-container Python applications, defining services, networks, and volumes in a declarative YAML format. It enables consistent development environments and simplifies service dependencies management.
'''
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/app
environment:
- FLASK_ENV=development
depends_on:
- db
db:
image: postgres:13
environment:
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
'''
Python Database Integration
The integration of Python applications with databases in Docker requires proper configuration of connection parameters and environment variables, ensuring data persistence and secure communication between containers.
import os
from sqlalchemy import create_engine
from flask_sqlalchemy import SQLAlchemy
from flask import Flask
app = Flask(__name__)
# Database configuration
db_params = {
'user': os.environ.get('POSTGRES_USER', 'myuser'),
'password': os.environ.get('POSTGRES_PASSWORD', 'mypassword'),
'host': os.environ.get('DB_HOST', 'db'),
'port': os.environ.get('DB_PORT', '5432'),
'database': os.environ.get('POSTGRES_DB', 'mydb')
}
app.config['SQLALCHEMY_DATABASE_URI'] = (
f"postgresql://{db_params['user']}:{db_params['password']}"
f"@{db_params['host']}:{db_params['port']}/{db_params['database']}"
)
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
Development vs Production Configurations
Managing different Docker configurations for development and production environments requires careful consideration of security, performance, and debugging capabilities while maintaining consistency across deployments.
'''
# docker-compose.dev.yml
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
environment:
- FLASK_ENV=development
- DEBUG=1
# docker-compose.prod.yml
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile.prod
environment:
- FLASK_ENV=production
- DEBUG=0
deploy:
replicas: 3
restart_policy:
condition: on-failure
'''
Containerized Python Testing
Docker containers provide isolated environments for running Python tests, ensuring consistent test execution across different platforms and preventing environmental dependencies from affecting test results.
'''
# Dockerfile.test
FROM python:3.9-slim
WORKDIR /tests
COPY requirements.txt requirements-test.txt ./
RUN pip install -r requirements.txt -r requirements-test.txt
COPY tests/ ./tests/
COPY src/ ./src/
CMD ["pytest", "--cov=src", "tests/"]
'''
# test_example.py
import pytest
from src.math_operations import calculate_stats
def test_calculate_mean():
data = [1, 2, 3, 4, 5]
result = calculate_stats(data)
assert result['mean'] == 3.0
assert result['std'] == pytest.approx(1.58, rel=1e-2)
Python Microservices Architecture
Microservices architecture in Docker allows breaking down complex Python applications into smaller, independently deployable services that communicate through well-defined APIs and message queues.
# Service 1: User Authentication
from flask import Flask, jsonify
import jwt
app = Flask(__name__)
@app.route('/auth', methods=['POST'])
def authenticate():
# Authentication logic
token = jwt.encode(
{'user_id': 123, 'role': 'user'},
'secret_key',
algorithm='HS256'
)
return jsonify({'token': token})
'''
# docker-compose.yml for microservices
services:
auth_service:
build: ./auth
ports:
- "5000:5000"
data_service:
build: ./data
ports:
- "5001:5000"
depends_on:
- auth_service
'''
Environment Management
Proper environment variable management in Docker ensures secure configuration handling and flexibility across different deployment scenarios while maintaining application security.
import os
from dotenv import load_dotenv
# Config class for different environments
class Config:
def __init__(self):
load_dotenv()
@property
def database_url(self):
return os.getenv('DATABASE_URL')
@property
def api_key(self):
return os.getenv('API_KEY')
@property
def debug_mode(self):
return os.getenv('DEBUG', 'False').lower() == 'true'
'''
# .env.example
DATABASE_URL=postgresql://user:pass@localhost:5432/db
API_KEY=your_secret_key
DEBUG=False
# docker-compose.yml environment section
services:
web:
env_file:
- .env.production
environment:
- DEBUG=False
'''
Health Checks and Monitoring
Implementing health checks and monitoring in Dockerized Python applications ensures reliable operation and provides insights into application performance and container health status.
from flask import Flask
import prometheus_client
from prometheus_client import Counter, Histogram
import time
app = Flask(__name__)
# Prometheus metrics
REQUEST_COUNT = Counter(
'request_count', 'App Request Count',
['method', 'endpoint', 'http_status']
)
REQUEST_LATENCY = Histogram(
'request_latency_seconds',
'Request latency in seconds'
)
@app.route('/health')
def health_check():
return {
'status': 'healthy',
'timestamp': time.time(),
'version': '1.0.0',
'dependencies': {
'database': check_db_connection(),
'cache': check_cache_status()
}
}
'''
# Docker health check configuration
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
'''
Container Resource Management
Managing container resources effectively ensures optimal performance of Python applications while preventing resource exhaustion. Docker allows precise control over CPU, memory, and other system resources.
'''
# docker-compose.yml with resource constraints
version: '3.8'
services:
web:
build: .
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
'''
# Memory-aware Python code
import psutil
import resource
def monitor_resources():
# Get container memory limits
with open('/sys/fs/cgroup/memory/memory.limit_in_bytes') as f:
memory_limit = int(f.read())
# Current usage
process = psutil.Process()
memory_info = process.memory_info()
return {
'memory_usage': memory_info.rss,
'memory_limit': memory_limit,
'cpu_percent': process.cpu_percent()
}
Network Configuration and Service Discovery
Docker networking enables seamless communication between containerized Python services while providing service discovery capabilities for dynamic microservice architectures.
from consul import Consul
import socket
class ServiceRegistry:
def __init__(self):
self.consul = Consul(host='consul')
self.service_name = 'python-app'
def register(self):
service_id = f"{self.service_name}-{socket.gethostname()}"
self.consul.agent.service.register(
name=self.service_name,
service_id=service_id,
address=socket.gethostname(),
port=5000,
tags=['python', 'api'],
check=self.get_health_check()
)
'''
# Network configuration in docker-compose.yml
version: '3.8'
services:
app:
networks:
- backend
- frontend
networks:
backend:
driver: bridge
internal: true
frontend:
driver: bridge
'''
Security Best Practices
Implementing security measures in Dockerized Python applications involves proper user permissions, secure secrets management, and runtime protection mechanisms.
'''
# Secure Dockerfile configuration
FROM python:3.9-slim
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Set working directory and permissions
WORKDIR /app
COPY --chown=appuser:appuser . .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Switch to non-root user
USER appuser
# Use security options
EXPOSE 5000
CMD ["python", "secure_app.py"]
'''
# Secure application configuration
import secrets
from cryptography.fernet import Fernet
class SecureConfig:
def __init__(self):
self.key = Fernet.generate_key()
self.cipher_suite = Fernet(self.key)
def encrypt_secret(self, secret):
return self.cipher_suite.encrypt(secret.encode())
def decrypt_secret(self, encrypted_secret):
return self.cipher_suite.decrypt(encrypted_secret).decode()
Real-world Application: ML Model Deployment
This implementation demonstrates deploying a machine learning model using Docker, including data preprocessing, model serving, and API endpoint creation.
from flask import Flask, request, jsonify
import joblib
import numpy as np
from sklearn.preprocessing import StandardScaler
app = Flask(__name__)
# Load model and scaler
model = joblib.load('model.pkl')
scaler = joblib.load('scaler.pkl')
@app.route('/predict', methods=['POST'])
def predict():
data = request.json['data']
features = np.array(data).reshape(1, -1)
scaled_features = scaler.transform(features)
prediction = model.predict(scaled_features)
return jsonify({
'prediction': prediction.tolist(),
'probability': model.predict_proba(scaled_features).tolist()
})
'''
# ML Model Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY model.pkl scaler.pkl ./
COPY app.py .
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
'''
Results for ML Model Deployment
This slide presents the performance metrics and deployment results for the machine learning model containerization implementation from the previous slide.
# Performance Testing Results
import requests
import time
import statistics
def test_model_performance():
url = "http://localhost:5000/predict"
latencies = []
# Test data
test_data = {"data": [1.2, 0.5, 3.2, 2.1]}
# Perform 1000 requests
for _ in range(1000):
start_time = time.time()
response = requests.post(url, json=test_data)
latency = (time.time() - start_time) * 1000 # ms
latencies.append(latency)
return {
"avg_latency": statistics.mean(latencies),
"p95_latency": statistics.quantiles(latencies, n=20)[18],
"success_rate": sum(1 for l in latencies if l < 100) / len(latencies),
"memory_usage": "256MB",
"container_cpu": "0.2 cores"
}
'''
# Example Output:
{
"avg_latency": 12.5,
"p95_latency": 18.3,
"success_rate": 0.995,
"memory_usage": "256MB",
"container_cpu": "0.2 cores"
}
'''
Real-world Application: Distributed Task Processing
Implementation of a distributed task processing system using Docker Compose, Redis for message queuing, and Celery for task execution.
from celery import Celery
from flask import Flask
import redis
app = Flask(__name__)
celery = Celery('tasks', broker='redis://redis:6379/0')
redis_client = redis.Redis(host='redis', port=6379, db=0)
@celery.task
def process_data(data):
# Complex data processing
result = perform_computation(data)
redis_client.set(f"result_{data['id']}", str(result))
return result
@app.route('/process', methods=['POST'])
def submit_task():
task = process_data.delay(request.json)
return jsonify({'task_id': task.id})
'''
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
depends_on:
- redis
- worker
worker:
build: .
command: celery -A tasks worker --loglevel=info
depends_on:
- redis
redis:
image: redis:6.2-alpine
'''
Additional Resources
- arXiv:2006.14800 - "Containerized Deep Learning with Docker: A Comprehensive Guide" https://arxiv.org/abs/2006.14800
- arXiv:2104.12369 - "Best Practices for Scientific Computing in Containers" https://arxiv.org/abs/2104.12369
- arXiv:1905.05178 - "Performance Analysis of Containerized Machine Learning Services" https://arxiv.org/abs/1905.05178
- arXiv:2003.12992 - "Microservices in Python: Design Patterns and Implementation" https://arxiv.org/abs/2003.12992
- arXiv:2102.04959 - "Container Orchestration for Scientific Workflows" https://arxiv.org/abs/2102.04959