Deployment Guide

Complete guide to deploying PutPlace in production environments.

Overview

This guide covers:

  • Production server setup

  • HTTPS/TLS configuration

  • Reverse proxy setup (nginx, traefik)

  • Container deployment (Docker, Docker Compose, Kubernetes)

  • Monitoring and logging

  • Backup strategies

  • High availability

Prerequisites

Before deploying to production:

  • PutPlace installed and tested locally

  • MongoDB configured and accessible

  • Storage backend configured (local or S3)

  • API keys created

  • Domain name (for HTTPS)

  • SSL certificate (Let’s Encrypt recommended)

Quick Deploy Options

Other Deployment Options

For other deployment platforms and methods, see the sections below:

Production Architecture

Simple Deployment

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Internetβ”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
     β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   nginx     β”‚  (Reverse proxy, TLS termination)
β”‚  Port 443   β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PutPlace   β”‚  (Gunicorn + Uvicorn workers)
β”‚  Port 8000  β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     β”‚
β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  MongoDB    β”‚
β”‚  Port 27017 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

High Availability Deployment

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Internet β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
      β”‚
β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
β”‚Load Balancerβ”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
      β”‚
   β”Œβ”€β”€β”΄β”€β”€β”
   β”‚     β”‚
   β–Ό     β–Ό
β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”
β”‚App1β”‚ β”‚App2β”‚  (Multiple PutPlace instances)
β””β”€β”¬β”€β”€β”˜ β””β”€β”¬β”€β”€β”˜
  β”‚      β”‚
  β””β”€β”€β”€β”¬β”€β”€β”˜
      β”‚
β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”
β”‚ MongoDB   β”‚  (Replica set)
β”‚ Cluster   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚
β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”
β”‚    S3     β”‚  (Shared storage)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Method 2: Docker

Dockerfile

Create Dockerfile:

FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements
COPY pyproject.toml setup.py ./
COPY src/ ./src/

# Install Python dependencies
RUN pip install --no-cache-dir -e ".[s3]"

# Create non-root user
RUN useradd -m -u 1000 putplace && \
    chown -R putplace:putplace /app
USER putplace

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD curl -f http://localhost:8000/health || exit 1

# Run with Gunicorn
CMD ["gunicorn", "putplace.main:app", \
     "--workers", "4", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--bind", "0.0.0.0:8000", \
     "--access-logfile", "-", \
     "--error-logfile", "-"]

Build and Run

# Build image
docker build -t putplace:latest .

# Run container
docker run -d \
  --name putplace \
  -p 8000:8000 \
  -e MONGODB_URL=mongodb://mongodb:27017 \
  -e MONGODB_DATABASE=putplace \
  -e STORAGE_BACKEND=s3 \
  -e S3_BUCKET_NAME=putplace-production \
  -e S3_REGION_NAME=us-east-1 \
  -e AWS_ACCESS_KEY_ID=... \
  -e AWS_SECRET_ACCESS_KEY=... \
  --restart unless-stopped \
  putplace:latest

# Check logs
docker logs -f putplace

Method 3: Docker Compose

docker-compose.yml

version: '3.8'

services:
  mongodb:
    image: mongo:6
    container_name: putplace-mongodb
    volumes:
      - mongodb-data:/data/db
    environment:
      MONGO_INITDB_DATABASE: putplace
    networks:
      - putplace-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5

  putplace:
    image: putplace:latest
    container_name: putplace-api
    depends_on:
      mongodb:
        condition: service_healthy
    ports:
      - "8000:8000"
    environment:
      MONGODB_URL: mongodb://mongodb:27017
      MONGODB_DATABASE: putplace
      STORAGE_BACKEND: s3
      S3_BUCKET_NAME: putplace-production
      S3_REGION_NAME: us-east-1
    env_file:
      - .env  # For AWS credentials
    networks:
      - putplace-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  nginx:
    image: nginx:alpine
    container_name: putplace-nginx
    depends_on:
      - putplace
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
      - nginx-cache:/var/cache/nginx
    networks:
      - putplace-network
    restart: unless-stopped

volumes:
  mongodb-data:
  nginx-cache:

networks:
  putplace-network:
    driver: bridge

Start Services

# Start all services
docker-compose up -d

# View logs
docker-compose logs -f

# Stop services
docker-compose down

# Update services
docker-compose pull
docker-compose up -d

HTTPS Configuration

Option 1: Let’s Encrypt with Certbot

Install Certbot

# Ubuntu/Debian
sudo apt install certbot python3-certbot-nginx

# CentOS/RHEL
sudo yum install certbot python3-certbot-nginx

Obtain Certificate

# Automatic nginx configuration
sudo certbot --nginx -d putplace.example.com

# Or manual certificate only
sudo certbot certonly --standalone -d putplace.example.com

Auto-renewal

# Test renewal
sudo certbot renew --dry-run

# Certbot automatically adds cron job for renewal

Option 2: Manual nginx Configuration

nginx Configuration

sudo nano /etc/nginx/sites-available/putplace

Complete nginx config:

# Redirect HTTP to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name putplace.example.com;

    # ACME challenge for Let's Encrypt
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # Redirect all other requests to HTTPS
    location / {
        return 301 https://$server_name$request_uri;
    }
}

# HTTPS server
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name putplace.example.com;

    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/putplace.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/putplace.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Logging
    access_log /var/log/nginx/putplace-access.log;
    error_log /var/log/nginx/putplace-error.log;

    # Proxy to PutPlace
    location / {
        proxy_pass http://127.0.0.1:8000;
        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 for large file uploads
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
        send_timeout 300s;

        # Max body size for file uploads
        client_max_body_size 100M;

        # Buffer settings
        proxy_buffering off;
        proxy_request_buffering off;
    }

    # Health check endpoint (no auth required)
    location /health {
        proxy_pass http://127.0.0.1:8000/health;
        access_log off;
    }
}

Enable Site

# Enable site
sudo ln -s /etc/nginx/sites-available/putplace /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload nginx
sudo systemctl reload nginx

Monitoring and Logging

Application Logs

View logs:

# systemd service
sudo journalctl -u putplace -f

# Docker
docker logs -f putplace

# Docker Compose
docker-compose logs -f putplace

Log rotation:

Create /etc/logrotate.d/putplace:

/var/log/putplace/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 putplace putplace
    sharedscripts
    postrotate
        systemctl reload putplace > /dev/null 2>&1 || true
    endscript
}

System Monitoring

Prometheus + Grafana

Install Prometheus exporter:

pip install prometheus-fastapi-instrumentator

Update main.py:

from prometheus_fastapi_instrumentator import Instrumentator

app = FastAPI(...)

# Add Prometheus metrics
Instrumentator().instrument(app).expose(app)

Access metrics:

http://localhost:8000/metrics

Health Check Monitoring

Create monitoring script:

#!/bin/bash
# /usr/local/bin/check-putplace-health.sh

URL="https://putplace.example.com/health"
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" $URL)

if [ "$RESPONSE" != "200" ]; then
    echo "PutPlace health check failed: HTTP $RESPONSE"
    # Send alert (email, Slack, PagerDuty, etc.)
    exit 1
fi

echo "PutPlace health check passed"
exit 0

Add to cron:

*/5 * * * * /usr/local/bin/check-putplace-health.sh

Backup Strategy

MongoDB Backup

Automated backup script:

#!/bin/bash
# /usr/local/bin/backup-putplace-mongodb.sh

BACKUP_DIR="/backup/putplace/mongodb"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

# Create backup
mongodump --db putplace --out "$BACKUP_DIR/$DATE"

# Compress backup
tar -czf "$BACKUP_DIR/$DATE.tar.gz" -C "$BACKUP_DIR" "$DATE"
rm -rf "$BACKUP_DIR/$DATE"

# Remove old backups
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup completed: $BACKUP_DIR/$DATE.tar.gz"

Add to cron:

0 2 * * * /usr/local/bin/backup-putplace-mongodb.sh

Storage Backup

Local storage:

rsync -av /var/putplace/files/ /backup/putplace/files/

S3 storage:

S3 is already highly durable. Enable versioning and cross-region replication for additional protection.

High Availability

Load Balancing

nginx upstream configuration:

upstream putplace_backend {
    least_conn;
    server putplace-01:8000 max_fails=3 fail_timeout=30s;
    server putplace-02:8000 max_fails=3 fail_timeout=30s;
    server putplace-03:8000 max_fails=3 fail_timeout=30s;
}

server {
    listen 443 ssl http2;
    server_name putplace.example.com;

    location / {
        proxy_pass http://putplace_backend;
        ...
    }
}

MongoDB Replica Set

Configure replica set:

# On each MongoDB node
mongod --replSet rs0 --bind_ip_all

# Initialize replica set (on primary)
mongosh
> rs.initiate({
    _id: "rs0",
    members: [
      { _id: 0, host: "mongo-01:27017" },
      { _id: 1, host: "mongo-02:27017" },
      { _id: 2, host: "mongo-03:27017" }
    ]
  })

Update connection string:

MONGODB_URL=mongodb://mongo-01:27017,mongo-02:27017,mongo-03:27017/?replicaSet=rs0

Security Hardening

Firewall Configuration

# Allow SSH
sudo ufw allow 22/tcp

# Allow HTTP/HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Allow MongoDB (from app servers only)
sudo ufw allow from 10.0.1.0/24 to any port 27017

# Enable firewall
sudo ufw enable

Rate Limiting

nginx rate limiting:

# Define rate limit zone
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
    ...

    location /put_file {
        limit_req zone=api_limit burst=20 nodelay;
        proxy_pass http://127.0.0.1:8000;
    }
}

Fail2ban

Install:

sudo apt install fail2ban

Configure for nginx:

Create /etc/fail2ban/filter.d/nginx-4xx.conf:

[Definition]
failregex = ^<HOST> .* "(GET|POST|PUT) .* HTTP.*" 4\d{2}
ignoreregex =

Create /etc/fail2ban/jail.local:

[nginx-4xx]
enabled = true
filter = nginx-4xx
logpath = /var/log/nginx/putplace-access.log
maxretry = 100
findtime = 60
bantime = 3600

Troubleshooting Production Issues

Service Won’t Start

# Check service status
sudo systemctl status putplace

# Check logs
sudo journalctl -u putplace -n 100 --no-pager

# Check configuration
/opt/putplace/.venv/bin/python -m putplace.main:app

High Memory Usage

# Check memory usage
ps aux | grep gunicorn

# Reduce worker count
# Edit /etc/systemd/system/putplace.service
# Change --workers value

# Restart service
sudo systemctl restart putplace

Slow Response Times

# Check MongoDB performance
mongosh --eval "db.serverStatus()"

# Check application logs
sudo journalctl -u putplace -f

# Check nginx access logs
sudo tail -f /var/log/nginx/putplace-access.log

# Add indexes if needed
mongosh putplace --eval "db.file_metadata.getIndexes()"

Next Steps