Deployment Guide
Deployment Guide
This guide covers deploying PyFrame applications to production environments, from simple cloud platforms to enterprise infrastructure.
🚀 Quick Deployment Options
1. Platform-as-a-Service (PaaS)
Heroku
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Install Heroku CLI and create app
heroku create my-pyframe-app
# Add Python buildpack
heroku buildpacks:set heroku/python
# Create Procfile
echo "web: python main.py" > Procfile
# Create requirements.txt
echo "pyframe>=0.1.0" > requirements.txt
echo "gunicorn>=20.1.0" >> requirements.txt
echo "psycopg2-binary>=2.9.0" >> requirements.txt
# Configure environment
heroku config:set PYFRAME_ENV=production
heroku config:set DEBUG=False
heroku config:set SECRET_KEY="your-secret-key-here"
# Add database
heroku addons:create heroku-postgresql:mini
# Deploy
git add .
git commit -m "Deploy to Heroku"
git push heroku main
Railway
1
2
3
4
5
6
7
8
9
10
11
12
13
# railway.toml
[build]
builder = "NIXPACKS"
[deploy]
startCommand = "python main.py"
healthcheckPath = "/health"
healthcheckTimeout = 300
restartPolicyType = "ON_FAILURE"
[env]
PYFRAME_ENV = "production"
PORT = { default = "3000" }
Render
1
2
3
4
5
6
7
8
9
10
11
12
# render.yaml
services:
- type: web
name: pyframe-app
env: python
buildCommand: "pip install -r requirements.txt"
startCommand: "python main.py"
envVars:
- key: PYFRAME_ENV
value: production
- key: DEBUG
value: false
2. Docker Deployment
Basic Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
FROM python:3.11-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PYFRAME_ENV=production
# Set work directory
WORKDIR /app
# Install system dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
postgresql-client \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy project
COPY . .
# Create non-root user
RUN adduser --disabled-password --gecos '' appuser
RUN chown -R appuser:appuser /app
USER appuser
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Run application
CMD ["python", "main.py"]
Production Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
FROM python:3.11-slim as builder
# Build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
postgresql-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt
# Production stage
FROM python:3.11-slim
# Runtime dependencies
RUN apt-get update && apt-get install -y \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Copy wheels and install
COPY --from=builder /wheels /wheels
COPY requirements.txt .
RUN pip install --no-cache /wheels/*
# Application setup
WORKDIR /app
COPY . .
# Security
RUN adduser --disabled-password --gecos '' appuser
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 3000
CMD ["gunicorn", "--bind", "0.0.0.0:3000", "--workers", "4", "main:app"]
Docker Compose
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- PYFRAME_ENV=production
- DATABASE_URL=postgresql://postgres:password@db:5432/pyframe_db
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
volumes:
- ./static:/app/static
- ./media:/app/media
db:
image: postgres:15
environment:
- POSTGRES_DB=pyframe_db
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
- ./static:/var/www/static
depends_on:
- web
volumes:
postgres_data:
redis_data:
⚙️ Production Configuration
Application Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# config/production.py
import os
from pyframe import PyFrameConfig
config = PyFrameConfig(
# Server settings
debug=False,
host="0.0.0.0",
port=int(os.getenv("PORT", 3000)),
hot_reload=False,
# Security
secret_key=os.getenv("SECRET_KEY"),
allowed_hosts=os.getenv("ALLOWED_HOSTS", "").split(","),
secure_cookies=True,
session_cookie_secure=True,
csrf_protection=True,
# Database
database_url=os.getenv("DATABASE_URL"),
database_pool_size=20,
database_max_overflow=30,
# Caching
cache_backend="redis",
cache_url=os.getenv("REDIS_URL"),
cache_default_timeout=300,
# Static files
static_url="/static/",
static_root=os.path.join(os.getcwd(), "staticfiles"),
media_url="/media/",
media_root=os.path.join(os.getcwd(), "media"),
# Logging
log_level="INFO",
log_format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
# Performance
enable_gzip=True,
gzip_level=6,
max_request_size=10 * 1024 * 1024, # 10MB
# Security headers
security_headers={
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"Content-Security-Policy": "default-src 'self'",
}
)
Environment Variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# .env.production
PYFRAME_ENV=production
DEBUG=False
SECRET_KEY=your-super-secret-key-here
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/pyframe_prod
DATABASE_POOL_SIZE=20
# Cache
REDIS_URL=redis://localhost:6379/0
# Security
ALLOWED_HOSTS=example.com,www.example.com
SECURE_COOKIES=True
# External services
EMAIL_URL=smtp://user:password@smtp.example.com:587
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_STORAGE_BUCKET_NAME=your-bucket
# Monitoring
SENTRY_DSN=https://your-sentry-dsn@sentry.io/project
MONITORING_ENABLED=True
🌐 Web Server Configuration
Nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# nginx.conf
upstream pyframe_app {
server web:3000;
# Add more servers for load balancing
# server web2:3000;
# server web3:3000;
}
server {
listen 80;
server_name example.com www.example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL configuration
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
# Gzip compression
gzip on;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Static files
location /static/ {
alias /var/www/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /var/www/media/;
expires 1y;
add_header Cache-Control "public";
}
# WebSocket support
location /ws/ {
proxy_pass http://pyframe_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
proxy_read_timeout 86400;
}
# Main application
location / {
proxy_pass http://pyframe_app;
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;
# Timeouts
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# Buffer settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
# Health check
location /health {
proxy_pass http://pyframe_app/health;
access_log off;
}
}
Apache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# apache.conf
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/html
# Redirect to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/html
# SSL Configuration
SSLEngine on
SSLCertificateFile /etc/ssl/certs/fullchain.pem
SSLCertificateKeyFile /etc/ssl/private/privkey.pem
# Static files
Alias /static /var/www/static
<Directory "/var/www/static">
Require all granted
ExpiresActive On
ExpiresDefault "access plus 1 year"
</Directory>
# Proxy to PyFrame app
ProxyPreserveHost On
ProxyPass /static !
ProxyPass /media !
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
# WebSocket support
ProxyPass /ws/ ws://localhost:3000/ws/
ProxyPassReverse /ws/ ws://localhost:3000/ws/
</VirtualHost>
🏗️ Infrastructure as Code
Terraform (AWS)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "pyframe-vpc"
}
}
# Subnets
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "pyframe-public-${count.index + 1}"
}
}
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 10}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "pyframe-private-${count.index + 1}"
}
}
# ECS Cluster
resource "aws_ecs_cluster" "main" {
name = "pyframe-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
# RDS Database
resource "aws_db_instance" "main" {
identifier = "pyframe-db"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.micro"
allocated_storage = 20
max_allocated_storage = 100
storage_type = "gp2"
storage_encrypted = true
db_name = "pyframe"
username = var.db_username
password = var.db_password
vpc_security_group_ids = [aws_security_group.rds.id]
db_subnet_group_name = aws_db_subnet_group.main.name
backup_retention_period = 7
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
skip_final_snapshot = true
deletion_protection = false
tags = {
Name = "pyframe-database"
}
}
# ElastiCache (Redis)
resource "aws_elasticache_subnet_group" "main" {
name = "pyframe-cache-subnet"
subnet_ids = aws_subnet.private[*].id
}
resource "aws_elasticache_cluster" "main" {
cluster_id = "pyframe-redis"
engine = "redis"
node_type = "cache.t3.micro"
num_cache_nodes = 1
parameter_group_name = "default.redis7"
port = 6379
subnet_group_name = aws_elasticache_subnet_group.main.name
security_group_ids = [aws_security_group.redis.id]
tags = {
Name = "pyframe-cache"
}
}
# Application Load Balancer
resource "aws_lb" "main" {
name = "pyframe-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public[*].id
enable_deletion_protection = false
tags = {
Name = "pyframe-alb"
}
}
Kubernetes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: pyframe
---
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pyframe-app
namespace: pyframe
spec:
replicas: 3
selector:
matchLabels:
app: pyframe-app
template:
metadata:
labels:
app: pyframe-app
spec:
containers:
- name: pyframe
image: your-registry/pyframe-app:latest
ports:
- containerPort: 3000
env:
- name: PYFRAME_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: pyframe-secrets
key: database-url
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: pyframe-secrets
key: redis-url
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: pyframe-secrets
key: secret-key
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: pyframe-service
namespace: pyframe
spec:
selector:
app: pyframe-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: ClusterIP
---
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: pyframe-ingress
namespace: pyframe
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- example.com
secretName: pyframe-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: pyframe-service
port:
number: 80
📊 Monitoring and Logging
Application Monitoring
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# monitoring.py
import os
import logging
from pyframe.monitoring import setup_monitoring
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler('/var/log/pyframe/app.log')
]
)
# Setup Sentry for error tracking
if os.getenv('SENTRY_DSN'):
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
sentry_logging = LoggingIntegration(
level=logging.INFO,
event_level=logging.ERROR
)
sentry_sdk.init(
dsn=os.getenv('SENTRY_DSN'),
integrations=[sentry_logging],
traces_sample_rate=0.1,
environment=os.getenv('PYFRAME_ENV', 'development')
)
# Setup Prometheus metrics
if os.getenv('PROMETHEUS_ENABLED'):
from prometheus_client import Counter, Histogram, generate_latest
REQUEST_COUNT = Counter('pyframe_requests_total', 'Total requests', ['method', 'endpoint'])
REQUEST_DURATION = Histogram('pyframe_request_duration_seconds', 'Request duration')
@app.middleware
async def metrics_middleware(context, call_next):
start_time = time.time()
response = await call_next(context)
REQUEST_COUNT.labels(context.method, context.path).inc()
REQUEST_DURATION.observe(time.time() - start_time)
return response
@app.route('/metrics')
async def metrics(context):
return {
'status': 200,
'headers': {'Content-Type': 'text/plain'},
'body': generate_latest()
}
Health Checks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# health.py
from pyframe import Component
from pyframe.database import get_db_connection
from pyframe.cache import get_cache_connection
class HealthCheck(Component):
async def check_database(self):
try:
conn = await get_db_connection()
await conn.execute('SELECT 1')
return {'status': 'healthy', 'latency': '< 10ms'}
except Exception as e:
return {'status': 'unhealthy', 'error': str(e)}
async def check_cache(self):
try:
cache = get_cache_connection()
await cache.set('health_check', 'ok', expire=10)
result = await cache.get('health_check')
return {'status': 'healthy' if result == 'ok' else 'unhealthy'}
except Exception as e:
return {'status': 'unhealthy', 'error': str(e)}
async def render(self):
db_health = await self.check_database()
cache_health = await self.check_cache()
overall_status = 'healthy' if (
db_health['status'] == 'healthy' and
cache_health['status'] == 'healthy'
) else 'unhealthy'
return {
'status': overall_status,
'checks': {
'database': db_health,
'cache': cache_health
},
'timestamp': datetime.now().isoformat()
}
@app.route('/health')
async def health_check(context):
health = HealthCheck()
result = await health.render()
status_code = 200 if result['status'] == 'healthy' else 503
return {
'status': status_code,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(result)
}
🔒 Security Best Practices
SSL/TLS Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# security.py
from pyframe.security import SecurityMiddleware
security_config = {
# Force HTTPS
'force_https': True,
'https_redirect_permanent': True,
# Security headers
'security_headers': {
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Security-Policy': (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; "
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
"font-src 'self' https://fonts.gstatic.com; "
"img-src 'self' data: https:; "
"connect-src 'self' wss: ws:;"
)
},
# CSRF protection
'csrf_protection': True,
'csrf_cookie_secure': True,
'csrf_cookie_httponly': True,
# Session security
'session_cookie_secure': True,
'session_cookie_httponly': True,
'session_cookie_samesite': 'Strict',
# Rate limiting
'rate_limiting': {
'default': '100/hour',
'login': '5/minute',
'api': '1000/hour'
}
}
app.use_middleware(SecurityMiddleware(security_config))
Secrets Management
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# secrets.py
import os
from cryptography.fernet import Fernet
class SecretsManager:
def __init__(self):
# Use environment variable or secrets service
self.encryption_key = os.getenv('ENCRYPTION_KEY')
if not self.encryption_key:
raise ValueError("ENCRYPTION_KEY environment variable is required")
self.cipher = Fernet(self.encryption_key.encode())
def encrypt_secret(self, value):
return self.cipher.encrypt(value.encode()).decode()
def decrypt_secret(self, encrypted_value):
return self.cipher.decrypt(encrypted_value.encode()).decode()
def get_secret(self, key):
# Try environment first
value = os.getenv(key)
if value:
return value
# Try encrypted secrets file
encrypted_value = self.get_encrypted_secret(key)
if encrypted_value:
return self.decrypt_secret(encrypted_value)
raise ValueError(f"Secret '{key}' not found")
secrets = SecretsManager()
# Usage
database_password = secrets.get_secret('DATABASE_PASSWORD')
api_key = secrets.get_secret('EXTERNAL_API_KEY')
📈 Performance Optimization
Application Performance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# performance.py
from pyframe.performance import PerformanceMiddleware
performance_config = {
# Response compression
'gzip_compression': True,
'gzip_level': 6,
'gzip_min_size': 1024,
# Static file optimization
'static_file_caching': True,
'static_file_max_age': 31536000, # 1 year
# Database connection pooling
'db_pool_size': 20,
'db_max_overflow': 30,
'db_pool_timeout': 30,
# Caching
'cache_middleware': True,
'cache_default_timeout': 300,
# Request/response optimization
'max_request_size': 10 * 1024 * 1024, # 10MB
'request_timeout': 30,
}
app.use_middleware(PerformanceMiddleware(performance_config))
# Database query optimization
@app.middleware
async def query_optimization_middleware(context, call_next):
# Enable query debugging in development
if app.config.debug:
from pyframe.database import enable_query_logging
enable_query_logging()
response = await call_next(context)
# Log slow queries
if hasattr(context, 'db_queries'):
slow_queries = [q for q in context.db_queries if q['duration'] > 0.1]
if slow_queries:
logger.warning(f"Slow queries detected: {len(slow_queries)}")
return response
CDN Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# cdn.py
import os
CDN_CONFIG = {
'enabled': os.getenv('CDN_ENABLED', 'false').lower() == 'true',
'url': os.getenv('CDN_URL', ''),
'static_url': os.getenv('CDN_STATIC_URL', ''),
'media_url': os.getenv('CDN_MEDIA_URL', ''),
# CloudFront settings
'cloudfront_distribution_id': os.getenv('CLOUDFRONT_DISTRIBUTION_ID'),
# CloudFlare settings
'cloudflare_zone_id': os.getenv('CLOUDFLARE_ZONE_ID'),
'cloudflare_api_token': os.getenv('CLOUDFLARE_API_TOKEN'),
}
if CDN_CONFIG['enabled']:
app.config.static_url = CDN_CONFIG['static_url']
app.config.media_url = CDN_CONFIG['media_url']
🔄 CI/CD Pipeline
GitHub Actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest --cov=pyframe tests/
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
- name: Upload coverage
uses: codecov/codecov-action@v3
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to registry
uses: docker/login-action@v2
with:
registry: ${{ secrets.REGISTRY_URL }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ secrets.REGISTRY_URL }}/pyframe-app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: |
# Deploy using your preferred method
# kubectl, helm, terraform, etc.
echo "Deploying to production..."
📚 Best Practices Summary
1. Security
- Use HTTPS everywhere
- Implement proper authentication and authorization
- Keep dependencies updated
- Use environment variables for secrets
- Enable security headers
- Implement rate limiting
2. Performance
- Use a reverse proxy (Nginx/Apache)
- Enable compression and caching
- Optimize database queries
- Use a CDN for static files
- Monitor application performance
3. Reliability
- Implement health checks
- Use multiple application instances
- Set up proper logging and monitoring
- Have a backup and disaster recovery plan
- Test your deployment pipeline
4. Scalability
- Use container orchestration (Kubernetes/Docker Swarm)
- Implement horizontal scaling
- Use managed database services
- Cache frequently accessed data
- Monitor resource usage
This deployment guide covers the essential aspects of taking your PyFrame application from development to production. Choose the deployment strategy that best fits your needs and scale! 🚀