Skip to main content

Pre-Deployment Checklist

Before deploying to production, ensure:
1

Environment Variables

Configure production environment variables in .env:
  • Set FLASK_ENV=production
  • Generate secure SECRET_KEY
  • Configure production database credentials
2

Database Setup

  • Create production database
  • Run migrations: flask db upgrade
  • Verify database connection
3

Code Quality

  • Run Black formatter: black .
  • Run type checking: mypy app/
  • Review code against conventions
4

Security

  • Never commit .env file
  • Use strong SECRET_KEY
  • Enable HTTPS in production
  • Review CSRF protection

Environment Configuration

Production Environment Variables

Create a production .env file:
.env
FLASK_APP=app.py
FLASK_ENV=production

# Database Configuration
DB_USER=production_user
DB_PASSWORD=strong_secure_password
DB_HOST=production-db-host
DB_PORT=3306
DB_NAME=muebles_roble_db

# Security
SECRET_KEY=your-very-secure-random-secret-key-here
CRITICAL: The application will raise an error if SECRET_KEY is not set in production.

Generating a Secure SECRET_KEY

Generate a cryptographically secure secret key:
import secrets
print(secrets.token_hex(32))
Or using command line:
python -c "import secrets; print(secrets.token_hex(32))"

Database Setup

Create Production Database

CREATE DATABASE muebles_roble_db 
  CHARACTER SET utf8mb4 
  COLLATE utf8mb4_unicode_ci;

CREATE USER 'production_user'@'%' 
  IDENTIFIED BY 'strong_secure_password';

GRANT ALL PRIVILEGES ON muebles_roble_db.* 
  TO 'production_user'@'%';

FLUSH PRIVILEGES;

Run Migrations

Apply database schema:
flask db upgrade

Verify Database Connection

The run.py script includes a connection test:
python run.py
You should see:
Database connection successful!

Production Server Options

Never use Flask’s development server in production. It’s not designed for security or performance.

Install Gunicorn

pip install gunicorn

Run with Gunicorn

gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()"
Parameters:
  • -w 4 - Number of worker processes (adjust based on CPU cores)
  • -b 0.0.0.0:8000 - Bind to all interfaces on port 8000

Gunicorn Configuration File

Create gunicorn.conf.py:
gunicorn.conf.py
import multiprocessing

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

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

# Logging
accesslog = "-"
errorlog = "-"
loglevel = "info"

# Process naming
proc_name = "muebles_roble"
Run with config:
gunicorn -c gunicorn.conf.py "app:create_app()"

Option 2: uWSGI

Install uWSGI

pip install uwsgi

Run with uWSGI

uwsgi --http :8000 --wsgi-file run.py --callable app --processes 4 --threads 2

uWSGI Configuration File

Create uwsgi.ini:
uwsgi.ini
[uwsgi]
module = app:create_app()
callable = app

master = true
processes = 4
threads = 2

http = :8000
vacuum = true
die-on-term = true
Run with config:
uwsgi --ini uwsgi.ini

Reverse Proxy with Nginx

Install Nginx

# Ubuntu/Debian
sudo apt install nginx

# CentOS/RHEL
sudo yum install nginx

Nginx Configuration

Create /etc/nginx/sites-available/muebles-roble:
server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    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;
    }

    location /static {
        alias /path/to/app/static;
        expires 30d;
    }
}

Enable Site

sudo ln -s /etc/nginx/sites-available/muebles-roble /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

SSL/HTTPS with Let’s Encrypt

Install Certbot

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

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

Obtain SSL Certificate

sudo certbot --nginx -d your-domain.com -d www.your-domain.com

Auto-renewal

Certbot automatically sets up auto-renewal. Test it:
sudo certbot renew --dry-run

Systemd Service

Create Service File

Create /etc/systemd/system/muebles-roble.service:
[Unit]
Description=Muebles Roble Gunicorn Application
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/muebles-roble
Environment="PATH=/path/to/muebles-roble/venv/bin"
ExecStart=/path/to/muebles-roble/venv/bin/gunicorn \
    -c gunicorn.conf.py "app:create_app()"
Restart=always

[Install]
WantedBy=multi-user.target

Enable and Start Service

sudo systemctl daemon-reload
sudo systemctl enable muebles-roble
sudo systemctl start muebles-roble
sudo systemctl status muebles-roble

Service Management

# Start service
sudo systemctl start muebles-roble

# Stop service
sudo systemctl stop muebles-roble

# Restart service
sudo systemctl restart muebles-roble

# View logs
sudo journalctl -u muebles-roble -f

Docker Deployment (Optional)

Dockerfile

Dockerfile
FROM python:3.10.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY . .

# Expose port
EXPOSE 8000

# Run with gunicorn
CMD ["gunicorn", "-c", "gunicorn.conf.py", "app:create_app()"]

Docker Compose

docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - FLASK_ENV=production
    env_file:
      - .env
    depends_on:
      - db
    restart: always

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    restart: always

volumes:
  mysql_data:

Run with Docker Compose

docker-compose up -d

Monitoring and Logging

Application Logs

View application logs:
# Systemd service logs
sudo journalctl -u muebles-roble -f

# Docker logs
docker-compose logs -f web

Error Tracking

Consider integrating error tracking services:
  • Sentry
  • Rollbar
  • Bugsnag

Performance Optimization

Configure SQLAlchemy connection pooling:
config.py
SQLALCHEMY_ENGINE_OPTIONS = {
    'pool_size': 10,
    'pool_recycle': 3600,
    'pool_pre_ping': True,
}
Add indexes to frequently queried columns:
name = db.Column(db.String(50), nullable=False, unique=True, index=True)
In Nginx:
gzip on;
gzip_types text/plain text/css application/json application/javascript;
Set cache headers in Nginx:
location /static {
    expires 30d;
    add_header Cache-Control "public, immutable";
}

Security Best Practices

Environment Variables

  • Never commit .env to version control
  • Use strong SECRET_KEY
  • Rotate secrets regularly

HTTPS

  • Always use HTTPS in production
  • Enable HSTS headers
  • Use valid SSL certificates

Database Security

  • Use strong database passwords
  • Restrict database access by IP
  • Regular backups

Updates

  • Keep dependencies updated
  • Monitor security advisories
  • Apply security patches promptly

Backup Strategy

Database Backups

Create automated MySQL backups:
#!/bin/bash
# backup.sh

BACKUP_DIR="/backups/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
FILENAME="muebles_roble_$DATE.sql"

mysqldump -u $DB_USER -p$DB_PASSWORD $DB_NAME > "$BACKUP_DIR/$FILENAME"
gzip "$BACKUP_DIR/$FILENAME"

# Keep only last 30 days
find $BACKUP_DIR -type f -mtime +30 -delete
Schedule with cron:
0 2 * * * /path/to/backup.sh

Troubleshooting

  • Check SECRET_KEY is set in .env
  • Verify database connection
  • Review error logs: sudo journalctl -u muebles-roble
  • Ensure virtual environment is activated
  • Verify MySQL is running
  • Check credentials in .env
  • Test connection: python run.py
  • Check firewall rules
  • Ensure Gunicorn/uWSGI is running
  • Check Nginx configuration
  • Verify proxy_pass address
  • Review Nginx error logs
  • Check file ownership: chown -R www-data:www-data /path/to/app
  • Verify directory permissions
  • Check systemd service user

Next Steps

Environment Variables

Complete environment configuration reference

Setup Guide

Return to development setup

Project Structure

Understand the application architecture

Coding Conventions

Follow best practices

Build docs developers (and LLMs) love