Production deployment guide for Python AMR, covering Docker, environment configuration, scaling strategies, monitoring, and operational best practices.
- Overview
- Production Requirements
- Docker Deployment
- Environment Configuration
- Database Setup
- Scaling Strategies
- Monitoring & Logging
- Security Configuration
- Backup & Recovery
- Performance Tuning
- Troubleshooting
Python AMR supports three deployment patterns:
- Single-Instance - Development and small production (<100 req/min)
- Multi-Instance - High availability and scaling (100-10k req/min)
- Containerized - Docker/Kubernetes orchestration (enterprise scale)
┌─────────────────────────────────────────────────────┐
│ Load Balancer (Optional) │
│ nginx/HAProxy │
└────────────────────┬────────────────────────────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┬─────────────┬─────────────┐
│ API │ API │ API │
│ Instance 1 │ Instance 2 │ Instance 3 │
└──────┬──────┴──────┬──────┴──────┬──────┘
│ │ │
└─────────────┼─────────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┬─────────────┬─────────────┐
│ PostgreSQL │ DuckDB │ Reference │
│ (Metadata) │ (Outputs) │ Data │
└─────────────┴─────────────┴─────────────┘
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 4 cores | 8+ cores |
| RAM | 8 GB | 16+ GB |
| Disk | 50 GB | 100+ GB SSD |
| Network | 100 Mbps | 1 Gbps |
- OS: Ubuntu 22.04 LTS, RHEL 8+, or Debian 11+
- Python: 3.14.x (required)
- Database: PostgreSQL 14+ (recommended) or SQLite 3.35+
- Reverse Proxy: nginx 1.20+ or HAProxy 2.4+
- Container Runtime (optional): Docker 20.10+ or containerd 1.6+
Create Dockerfile in project root:
FROM python:3.14-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy requirements
COPY pyproject.toml uv.lock ./
# Install Python dependencies
RUN pip install --no-cache-dir -e ".[dev]"
# Copy application
COPY src/ ./src/
COPY data/ ./data/
COPY scripts/ ./scripts/
# Create non-root user
RUN useradd -m -u 1000 amr && chown -R amr:amr /app
USER amr
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Run application
CMD ["uvicorn", "amr.api.app:app", "--host", "0.0.0.0", "--port", "8000"]Create docker-compose.yml:
version: '3.8'
services:
api:
build: .
ports:
- "8000:8000"
environment:
- AMR_METADATA_DB_URL=postgresql+asyncpg://amr:password@postgres:5432/amr
- AMR_DUCKDB_PATH=/data/runs_duckdb.db
- AMR_PERSIST_QUEUE_WORKERS=4
- AMR_LOG_LEVEL=INFO
volumes:
- ./data:/app/data
- duckdb_data:/data
depends_on:
- postgres
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_USER=amr
- POSTGRES_PASSWORD=password
- POSTGRES_DB=amr
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- api
restart: unless-stopped
volumes:
postgres_data:
duckdb_data:# Build image
docker build -t python-amr:latest .
# Run with docker-compose
docker-compose up -d
# View logs
docker-compose logs -f api
# Check health
curl http://localhost:8000/healthCreate .env file:
# Database Configuration
AMR_METADATA_DB_URL=postgresql+asyncpg://user:pass@localhost:5432/amr
AMR_DUCKDB_PATH=/var/lib/amr/runs_duckdb.db
# Persistence Queue
AMR_PERSIST_QUEUE_WORKERS=4
AMR_PERSIST_QUEUE_MAXSIZE=4096
AMR_PERSIST_RETRY_MAX_RETRIES=3
AMR_PERSIST_RETRY_BACKOFF_MS=50
# Dead Letter Queue
AMR_DEAD_LETTER_MAX_JSON_BYTES=131072
AMR_DEAD_LETTER_MAX_ERROR_CHARS=4096
AMR_DEAD_LETTER_REDACT_KEYS=password,token,secret,api_key
# Logging
AMR_LOG_LEVEL=INFO
AMR_LOG_FORMAT=json # json or text
# API Configuration
AMR_API_HOST=0.0.0.0
AMR_API_PORT=8000
AMR_API_WORKERS=4 # For production with gunicorn
# Security
AMR_ALLOWED_ORIGINS=https://dashboard.example.com
AMR_ENABLE_CORS=true
# Reference Data
AMR_DATA_DIR=/var/lib/amr/data
AMR_SNAPSHOTS_DIR=/var/lib/amr/data/snapshots# Using systemd
sudo cp amr.env /etc/amr/amr.env
sudo chmod 600 /etc/amr/amr.env
# Source in service file
EnvironmentFile=/etc/amr/amr.env# Install PostgreSQL
sudo apt-get install postgresql-16
# Create database and user
sudo -u postgres psql <<EOF
CREATE USER amr WITH PASSWORD 'your_secure_password';
CREATE DATABASE amr OWNER amr;
GRANT ALL PRIVILEGES ON DATABASE amr TO amr;
EOF
# Run migrations
PYTHONPATH=src python -c "
from amr.repositories.run_repository import RunRepository
import asyncio
asyncio.run(RunRepository().migrate())
"Edit /etc/postgresql/16/main/postgresql.conf:
# Memory
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 16MB
# Connections
max_connections = 100
# Performance
checkpoint_completion_target = 0.9
wal_buffers = 16MB
# Logging
log_statement = 'mod' # Log all modifications
log_duration = on
log_min_duration_statement = 1000 # Log slow queries (>1s)# Create data directory
sudo mkdir -p /var/lib/amr/data
sudo chown amr:amr /var/lib/amr/data
# Initialize DuckDB
PYTHONPATH=src python -c "
import duckdb
conn = duckdb.connect('/var/lib/amr/data/runs_duckdb.db')
conn.execute('''
CREATE TABLE IF NOT EXISTS run_outputs (
run_id INTEGER NOT NULL,
row_index INTEGER NOT NULL,
input_value VARCHAR,
sir_result VARCHAR,
metadata JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (run_id, row_index)
)
''')
conn.close()
"# Increase async workers
export AMR_PERSIST_QUEUE_WORKERS=8
# Increase API workers (with gunicorn)
export AMR_API_WORKERS=8# Increase queue size
export AMR_PERSIST_QUEUE_MAXSIZE=8192
# DuckDB memory limit
export DUCKDB_MEMORY_LIMIT=8GBnginx.conf:
upstream amr_api {
least_conn;
server api1.internal:8000 max_fails=3 fail_timeout=30s;
server api2.internal:8000 max_fails=3 fail_timeout=30s;
server api3.internal:8000 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://amr_api;
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_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location /health {
access_log off;
proxy_pass http://amr_api;
}
}All instances connect to single PostgreSQL:
# Instance 1
export AMR_METADATA_DB_URL=postgresql+asyncpg://amr:pass@postgres.internal:5432/amr
# Instance 2
export AMR_METADATA_DB_URL=postgresql+asyncpg://amr:pass@postgres.internal:5432/amr
# Instance 3
export AMR_METADATA_DB_URL=postgresql+asyncpg://amr:pass@postgres.internal:5432/amrDuckDB is single-writer - use one of these strategies:
- Separate DuckDB per instance - Each instance has its own DuckDB file
- PostgreSQL for outputs - Switch output storage to PostgreSQL (slower)
- Write leader - One instance handles all writes, others read-only
Expose metrics endpoint:
# Configured in amr.api.app
@app.get("/metrics")
async def metrics():
"""Prometheus-compatible metrics endpoint."""
return PlainTextResponse(generate_metrics())prometheus.yml:
scrape_configs:
- job_name: 'python-amr'
static_configs:
- targets: ['api1.internal:8000', 'api2.internal:8000']
metrics_path: '/metrics'
scrape_interval: 30sKey metrics to monitor:
- Request rate -
amr_requests_total - Response time -
amr_request_duration_seconds - Queue depth -
amr_queue_size - Database connections -
amr_db_connections_active - Error rate -
amr_errors_total
Configure JSON logging:
export AMR_LOG_FORMAT=json
export AMR_LOG_LEVEL=INFOExample log entry:
{
"timestamp": "2026-02-15T12:00:00Z",
"level": "INFO",
"message": "Request processed",
"method": "POST",
"path": "/v1/sir/interpret",
"status_code": 200,
"duration_ms": 45.3,
"request_id": "abc123"
}Fluentd/Fluent Bit configuration:
# fluent-bit.conf
[INPUT]
Name tail
Path /var/log/amr/api.log
Parser json
[OUTPUT]
Name elasticsearch
Match *
Host elasticsearch.internal
Port 9200
Index amr-logsnginx SSL configuration:
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# HSTS
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
proxy_pass http://amr_api;
}
}# UFW (Ubuntu)
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw deny 8000/tcp # Block direct API access
sudo ufw enableEnable PostgreSQL SSL:
# postgresql.conf
ssl = on
ssl_cert_file = '/etc/ssl/certs/server.crt'
ssl_key_file = '/etc/ssl/private/server.key'Connection with SSL:
export AMR_METADATA_DB_URL=postgresql+asyncpg://user:pass@host:5432/amr?ssl=require# Daily backup script
#!/bin/bash
BACKUP_DIR=/var/backups/amr/postgres
DATE=$(date +%Y%m%d_%H%M%S)
pg_dump -U amr -Fc amr > $BACKUP_DIR/amr_$DATE.dump
# Retention: keep last 7 days
find $BACKUP_DIR -name "*.dump" -mtime +7 -delete
# Cron: 2 AM daily
# 0 2 * * * /usr/local/bin/backup_postgres.sh# Backup DuckDB file
#!/bin/bash
DUCKDB_PATH=/var/lib/amr/data/runs_duckdb.db
BACKUP_DIR=/var/backups/amr/duckdb
DATE=$(date +%Y%m%d_%H%M%S)
# Stop writes (optional)
systemctl stop amr-api
# Copy file
cp $DUCKDB_PATH $BACKUP_DIR/runs_duckdb_$DATE.db
# Restart
systemctl start amr-api
# Retention
find $BACKUP_DIR -name "*.db" -mtime +30 -delete# PostgreSQL restore
pg_restore -U amr -d amr /var/backups/amr/postgres/amr_20260215.dump
# DuckDB restore
systemctl stop amr-api
cp /var/backups/amr/duckdb/runs_duckdb_20260215.db /var/lib/amr/data/runs_duckdb.db
systemctl start amr-apiFor production, use gunicorn instead of uvicorn directly:
# Install
pip install gunicorn
# Run
gunicorn amr.api.app:app \
--worker-class uvicorn.workers.UvicornWorker \
--workers 4 \
--bind 0.0.0.0:8000 \
--timeout 60 \
--access-logfile /var/log/amr/access.log \
--error-logfile /var/log/amr/error.logCreate /etc/systemd/system/amr-api.service:
[Unit]
Description=Python AMR API
After=network.target postgresql.service
[Service]
Type=notify
User=amr
Group=amr
WorkingDirectory=/opt/amr
EnvironmentFile=/etc/amr/amr.env
ExecStart=/opt/amr/.venv/bin/gunicorn amr.api.app:app \
--worker-class uvicorn.workers.UvicornWorker \
--workers 4 \
--bind 0.0.0.0:8000 \
--timeout 60
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable amr-api
sudo systemctl start amr-api
sudo systemctl status amr-api# Check process memory
ps aux | grep gunicorn
# Reduce workers
export AMR_API_WORKERS=2
export AMR_PERSIST_QUEUE_WORKERS=2
# Set DuckDB memory limit
export DUCKDB_MEMORY_LIMIT=2GB# Check queue health
curl http://localhost:8000/v1/runs/queue
# Increase workers
export AMR_PERSIST_QUEUE_WORKERS=8
# Increase queue size
export AMR_PERSIST_QUEUE_MAXSIZE=8192# Check PostgreSQL connections
SELECT count(*) FROM pg_stat_activity;
# Increase pool size
export AMR_DB_POOL_SIZE=20
export AMR_DB_MAX_OVERFLOW=40
# Check connection limit
SELECT setting FROM pg_settings WHERE name = 'max_connections';# Check slow queries
tail -f /var/log/postgresql/postgresql-16-main.log | grep "duration"
# Analyze DuckDB
sqlite3 runs_duckdb.db "PRAGMA analyze"
# Check disk space
df -h /var/lib/amr- Architecture Overview - System design
- Configuration Reference - All settings
- Operations Runbook - Day-to-day ops
- Incident Response - Troubleshooting
Last Updated: 2026-02-15