Skip to main content

Python + Flask + Gunicorn - using uv

·1262 words·6 mins
Author
Dr. Suresh Ramasamy
A tech afficianado who hails from South East Asia.

I needed a good guide on deploying the PFUG stack as I now do that often (it helps me with rapid prototyping and MVP).

This guide provides comprehensive instructions for deploying a Python/Flask application in production using uv for dependency management and Gunicorn as the WSGI server.

Prerequisites
#

  • Ubuntu/Debian-based server (adaptable to other Linux distributions)
  • Python 3.8 or higher
  • Sudo access
  • Domain name (optional but recommended)

1. System Preparation
#

Update system packages
#

sudo apt update && sudo apt upgrade -y

Install required system dependencies
#

sudo apt install -y python3-dev python3-pip build-essential nginx supervisor curl

Install uv (Python package manager)
#

curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.cargo/env

2. Application Setup
#

Create application user and directory
#

# Create dedicated user for the application
sudo useradd -m -s /bin/bash flaskapp
sudo usermod -aG sudo flaskapp

# Create application directory
sudo mkdir -p /opt/flask-app
sudo chown flaskapp:flaskapp /opt/flask-app

Switch to application user and setup directory
#

sudo -u flaskapp -i
cd /opt/flask-app

Clone your application (replace with your repository)
#

# Example - replace with your actual repository
git clone https://github.com/yourusername/your-flask-app.git .
# OR upload your application files to this directory

3. Python Environment Setup with uv
#

Initialize uv project and install dependencies
#

# Initialize uv in the project directory
uv init --no-readme

# Install Flask and Gunicorn
uv add flask gunicorn

# Install your application dependencies
# If you have a requirements.txt:
uv pip install -r requirements.txt

# Or install specific packages:
# uv add requests psycopg2-binary redis etc.

Create pyproject.toml for dependency management
#

[project]
name = "flask-app"
version = "1.0.0"
description = "Production Flask Application"
requires-python = ">=3.8"
dependencies = [
    "flask>=2.3.0",
    "gunicorn>=21.2.0",
    # Add your other dependencies here
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

4. Application Configuration
#

Create Gunicorn configuration file
#

Create /opt/flask-app/gunicorn_config.py:

import multiprocessing
import os

# Server socket
bind = "127.0.0.1:8000"
backlog = 2048

# Worker processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2

# Restart workers after this many requests, to help prevent memory leaks
max_requests = 1000
max_requests_jitter = 100

# Process naming
proc_name = 'flask-app'

# Logging
accesslog = '/var/log/flask-app/access.log'
errorlog = '/var/log/flask-app/error.log'
loglevel = 'info'

# Process identification
pidfile = '/opt/flask-app/gunicorn.pid'

# User and group
user = 'flaskapp'
group = 'flaskapp'

# Preload application for better performance
preload_app = True

# Environment variables
raw_env = [
    'FLASK_ENV=production',
    'PYTHONPATH=/opt/flask-app',
]

Create environment configuration
#

Create /opt/flask-app/.env:

# Flask Configuration
FLASK_APP=app.py
FLASK_ENV=production
SECRET_KEY=your-super-secret-key-change-this

# Database Configuration (if using database)
DATABASE_URL=postgresql://user:password@localhost/dbname

# Other environment variables
# REDIS_URL=redis://localhost:6379/0
# MAIL_SERVER=smtp.gmail.com

Create log directories
#

sudo mkdir -p /var/log/flask-app
sudo chown flaskapp:flaskapp /var/log/flask-app

5. Supporting Scripts
#

Deployment Script
#

Create /opt/flask-app/deploy.sh:

#!/bin/bash

# Flask Application Deployment Script
set -e

APP_DIR="/opt/flask-app"
APP_USER="flaskapp"

echo "Starting deployment..."

# Navigate to app directory
cd $APP_DIR

# Pull latest changes (if using git)
echo "Pulling latest changes..."
sudo -u $APP_USER git pull origin main

# Update dependencies
echo "Updating dependencies..."
sudo -u $APP_USER uv sync

# Run database migrations (if applicable)
# echo "Running database migrations..."
# sudo -u $APP_USER uv run flask db upgrade

# Collect static files (if applicable)
# echo "Collecting static files..."
# sudo -u $APP_USER uv run python manage.py collectstatic --noinput

# Restart services
echo "Restarting services..."
sudo supervisorctl restart flask-app
sudo systemctl reload nginx

echo "Deployment completed successfully!"

Health Check Script
#

Create /opt/flask-app/health_check.sh:

#!/bin/bash

# Health check script for Flask application
APP_URL="http://127.0.0.1:8000"
HEALTH_ENDPOINT="/health"  # Implement this endpoint in your Flask app

# Check if Gunicorn process is running
if ! pgrep -f "gunicorn.*flask-app" > /dev/null; then
    echo "ERROR: Gunicorn process not found"
    exit 1
fi

# Check HTTP response
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" ${APP_URL}${HEALTH_ENDPOINT} || echo "000")

if [ "$HTTP_CODE" = "200" ]; then
    echo "OK: Application is healthy"
    exit 0
else
    echo "ERROR: HTTP $HTTP_CODE - Application health check failed"
    exit 1
fi

Backup Script
#

Create /opt/flask-app/backup.sh:

#!/bin/bash

# Backup script for Flask application
BACKUP_DIR="/opt/backups"
APP_DIR="/opt/flask-app"
DATE=$(date +%Y%m%d_%H%M%S)

# Create backup directory
mkdir -p $BACKUP_DIR

# Backup application files
echo "Backing up application files..."
tar -czf "$BACKUP_DIR/flask-app-$DATE.tar.gz" -C /opt flask-app

# Backup database (PostgreSQL example)
# echo "Backing up database..."
# sudo -u postgres pg_dump your_database > "$BACKUP_DIR/database-$DATE.sql"

# Clean old backups (keep last 7 days)
find $BACKUP_DIR -name "flask-app-*.tar.gz" -mtime +7 -delete
# find $BACKUP_DIR -name "database-*.sql" -mtime +7 -delete

echo "Backup completed: flask-app-$DATE.tar.gz"

Make scripts executable:

chmod +x /opt/flask-app/deploy.sh
chmod +x /opt/flask-app/health_check.sh
chmod +x /opt/flask-app/backup.sh

6. Process Management with Supervisor
#

Install and configure Supervisor
#

sudo apt install supervisor -y

Create /etc/supervisor/conf.d/flask-app.conf:

[program:flask-app]
command=/home/flaskapp/.local/bin/uv run gunicorn -c gunicorn_config.py app:app
directory=/opt/flask-app
user=flaskapp
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/flask-app/supervisor.log
environment=PATH="/home/flaskapp/.local/bin:/usr/local/bin:/usr/bin:/bin"

Note: Do not daemonise gunicorn with –daemon. That will be handled by Supervisor.

Start and enable Supervisor
#

sudo systemctl enable supervisor
sudo systemctl start supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start flask-app

7. Nginx Reverse Proxy Setup
#

Create /etc/nginx/sites-available/flask-app:

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;  # Replace with your domain

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;

    # Rate limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req zone=api burst=20 nodelay;

    # Static files (if you have them)
    location /static {
        alias /opt/flask-app/static;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Main application
    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
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # Buffer settings
        proxy_buffering on;
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_max_temp_file_size 1024m;
    }

    # Health check endpoint
    location /health {
        proxy_pass http://127.0.0.1:8000;
        access_log off;
    }
}

Enable the site
#

sudo ln -s /etc/nginx/sites-available/flask-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

8. SSL Certificate with Certbot (Optional but Recommended) #

# Install Certbot
sudo apt install certbot python3-certbot-nginx -y

# Obtain SSL certificate
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

# Auto-renewal is set up automatically
sudo certbot renew --dry-run

9. Monitoring and Maintenance
#

Set up log rotation
#

Create /etc/logrotate.d/flask-app:

/var/log/flask-app/*.log {
    daily
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    create 644 flaskapp flaskapp
    postrotate
        supervisorctl restart flask-app
    endscript
}

Cron jobs for maintenance
#

Add to crontab (sudo crontab -e):

# Daily backup at 2 AM
0 2 * * * /opt/flask-app/backup.sh

# Health check every 5 minutes
*/5 * * * * /opt/flask-app/health_check.sh

# Log rotation
0 0 * * * /usr/sbin/logrotate /etc/logrotate.d/flask-app

10. Deployment Commands
#

Initial deployment
#

# Start services
sudo supervisorctl start flask-app
sudo systemctl start nginx

# Check status
sudo supervisorctl status
sudo systemctl status nginx

Updates and maintenance
#

# Deploy updates
./deploy.sh

# Check application status
sudo supervisorctl status flask-app

# View logs
sudo supervisorctl tail -f flask-app
tail -f /var/log/flask-app/error.log

# Restart application
sudo supervisorctl restart flask-app

Troubleshooting
#

Common issues and solutions
#

  1. Permission denied errors: Ensure correct ownership of files

    sudo chown -R flaskapp:flaskapp /opt/flask-app
    
  2. Port already in use: Check for conflicting processes

    sudo lsof -i :8000
    
  3. 502 Bad Gateway: Check Gunicorn is running and accessible

    curl http://127.0.0.1:8000
    
  4. View application logs:

    tail -f /var/log/flask-app/error.log
    sudo supervisorctl tail -f flask-app
    

Security Considerations
#

  1. Firewall configuration:

    sudo ufw allow ssh
    sudo ufw allow 'Nginx Full'
    sudo ufw enable
    
  2. Keep system updated:

    sudo apt update && sudo apt upgrade -y
    
  3. Regular security audits:

    uv pip audit
    
  4. Environment variables: Never commit secrets to version control

  5. Database security: Use strong passwords and limit access

This production setup provides a robust, scalable foundation for your Flask application with proper process management, reverse proxy, SSL termination, and monitoring capabilities.