Skip to main content

Flask Application Deployment SOP

1. Overview

This Standard Operating Procedure (SOP) defines the complete deployment workflow for Python Flask applications at Bonton Softwares. It covers both development and production environments and provides exact, executable steps for our DevOps team.

Supported Environments:
• Development (dev) - Internal testing and QA
• Production (prod) - Live customer-facing deployments

Preconditions:
• You have received deployment approval from the project lead
• The application has passed code review and testing
• You have server access credentials
• The application repository is accessible

2. Requirements From the Dev Team

Before deployment can proceed, the development team MUST provide:

Git Repository:
• Repository URL and access credentials
• Branch naming: main (production), develop (development), feature/* (features)
• Tag format for releases: v1.0.0, v1.0.1, etc.

Python Version:
• Exact Python version (e.g., Python 3.10.12, Python 3.11.5)
• Specified in .python-version file or README.md

Dependency Management:
• requirements.txt file with pinned versions (package==1.2.3)
• OR pyproject.toml + poetry.lock if using Poetry
• Separate requirements-dev.txt for development-only dependencies

Environment Variables:
• Complete list of required environment variables
• Sample .env.example file with dummy values
• Documentation of what each variable controls

 

WSGI Entry Point:
• Main application file (e.g., wsgi.py, app.py, or run.py)
• Application factory pattern usage (create_app() function)
• Entry point must be documented clearly

Static Files:
• Location of static files (CSS, JS, images)
• Whether static files are served by Flask or need Nginx configuration
• Any build steps required (npm build, webpack, etc.)

Application Configuration:
• Config file structure (config.py or config/ directory)
• How configuration is loaded (from file, environment, or both)
• Any database migration scripts (Flask-Migrate, Alembic)

3. Server Requirements

Operating System:
• Ubuntu 22.04 LTS or Ubuntu 20.04 LTS
• Debian 11 (Bullseye) acceptable as alternative

Python Installation:
• Match the exact version specified by dev team
• Use deadsnakes PPA if version not in default repos
• Include python3-pip, python3-venv, python3-dev

System Packages Required:
• build-essential
• libpq-dev (for PostgreSQL)
• libmysqlclient-dev (for MySQL/MariaDB)
• nginx
• supervisor OR systemd (systemd preferred)
• git

Application Server:
• Gunicorn (production)
• Flask development server (development only)

Directory Structure Standard:
• Application root: /var/www/<appname>/
• Virtual environment: /var/www/<appname>/venv/
• Application code: /var/www/<appname>/app/
• Logs: /var/log/<appname>/
• Static files: /var/www/<appname>/static/ (if served by Nginx)

 

User and Permissions:
• Create dedicated user: www-data or <appname>
• Application files owned by deployment user
• www-data group for web server access
• Permissions: 755 for directories, 644 for files
• Logs directory: 755 with write access for app user

4. Deployment Process - Development Mode

This procedure deploys the application for internal testing using Flask's built-in development server.

Step 1: Access the Development Server
ssh devops@dev-server.bontonsoftwares.com

Step 2: Create Application Directory
sudo mkdir -p /var/www/<appname>
sudo chown $USER:$USER /var/www/<appname>
cd /var/www/<appname>

Step 3: Clone Repository
git clone <repository-url> app
cd app
git checkout develop

Step 4: Create Virtual Environment
python3.10 -m venv ../venv
source ../venv/bin/activate

Step 5: Install Dependencies
pip install --upgrade pip
pip install -r requirements.txt

Step 6: Configure Environment
cp .env.example .env.dev
nano .env.dev
# Edit with development-specific values:
# FLASK_ENV=development
# FLASK_DEBUG=True
# DATABASE_URL=<dev-database-url>
# SECRET_KEY=<dev-secret-key>

Step 7: Initialize Database (if required)
export FLASK_APP=wsgi.py
flask db upgrade
# OR
python manage.py db upgrade

Step 8: Start Development Server
export FLASK_APP=wsgi.py
export FLASK_ENV=development
flask run --host=0.0.0.0 --port=5000

Step 9: Verify Deployment
curl http://localhost:5000
curl http://localhost:5000/health  # if health endpoint exists

 

Log Locations (Development):
• Application logs: /var/log/<appname>/app.log
• Flask logs: stdout (terminal where flask run is executed)

Shutdown Procedure (Development):
• Press Ctrl+C in the terminal where Flask is running
• Deactivate virtual environment: deactivate

5. Deployment Process - Production Mode

This procedure deploys the application for production use with Gunicorn and Nginx.

Step 1: Access Production Server
ssh devops@prod-server.bontonsoftwares.com

Step 2: Install System Dependencies
sudo apt update
sudo apt install -y python3.10 python3.10-venv python3-pip python3-dev
sudo apt install -y build-essential libpq-dev nginx git

Step 3: Create Application Directory Structure
sudo mkdir -p /var/www/<appname>
sudo mkdir -p /var/log/<appname>
sudo useradd -r -s /bin/bash <appname>  # if user doesn't exist
sudo chown <appname>:www-data /var/www/<appname>
sudo chown <appname>:www-data /var/log/<appname>
sudo chmod 755 /var/www/<appname>
sudo chmod 755 /var/log/<appname>

Step 4: Clone Repository as Application User
sudo -u <appname> -i
cd /var/www/<appname>
git clone <repository-url> app
cd app
git checkout main
git pull origin main

Step 5: Create and Activate Virtual Environment
python3.10 -m venv /var/www/<appname>/venv
source /var/www/<appname>/venv/bin/activate

Step 6: Install Production Dependencies
pip install --upgrade pip
pip install -r requirements.txt
pip install gunicorn

Step 7: Configure Production Environment
cd /var/www/<appname>/app
cp .env.example .env.prod
nano .env.prod


# Edit with production values:
# FLASK_ENV=production
# FLASK_DEBUG=False
# DATABASE_URL=<prod-database-url>
# SECRET_KEY=<strong-prod-secret-key>
# Any other production-specific variables

Step 8: Run Database Migrations
export FLASK_APP=wsgi.py
flask db upgrade
# OR
python manage.py db upgrade
exit  # exit from <appname> user

Step 9: Create Gunicorn Configuration
sudo nano /var/www/<appname>/gunicorn_config.py

# Add the following content:
bind = '127.0.0.1:8000'
workers = 4  # (2 x number_of_cores) + 1
worker_class = 'sync'
timeout = 120
accesslog = '/var/log/<appname>/gunicorn-access.log'
errorlog = '/var/log/<appname>/gunicorn-error.log'
loglevel = 'info'

Step 10: Create Systemd Service File
sudo nano /etc/systemd/system/<appname>.service

# Add the following content:
[Unit]
Description=Gunicorn instance for <appname> Flask application
After=network.target

[Service]
User=<appname>
Group=www-data
WorkingDirectory=/var/www/<appname>/app
Environment="PATH=/var/www/<appname>/venv/bin"
Environment="FLASK_APP=wsgi.py"
EnvironmentFile=/var/www/<appname>/app/.env.prod
ExecStart=/var/www/<appname>/venv/bin/gunicorn --config /var/www/<appname>/gunicorn_config.py wsgi:app
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

 

Step 11: Configure Nginx Reverse Proxy
sudo nano /etc/nginx/sites-available/<appname>

# Add the following configuration:
server {
    listen 80;
    server_name <domain.com> www.<domain.com>;

    access_log /var/log/nginx/<appname>-access.log;
    error_log /var/log/nginx/<appname>-error.log;

    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;
        proxy_redirect off;
        proxy_buffering off;
    }

    # Optional: Serve static files directly via Nginx
    location /static {
        alias /var/www/<appname>/app/static;
        expires 30d;
        access_log off;
    }

    # Optional: Serve media files
    location /media {
        alias /var/www/<appname>/app/media;
        expires 30d;
        access_log off;
    }
}

Step 12: Enable Nginx Site
sudo ln -s /etc/nginx/sites-available/<appname> /etc/nginx/sites-enabled/
sudo nginx -t  # Test configuration
sudo systemctl reload nginx

Step 13: Set File Permissions
sudo chown -R <appname>:www-data /var/www/<appname>
sudo chmod -R 755 /var/www/<appname>
sudo chmod 644 /var/www/<appname>/app/.env.prod

Step 14: Start the Application Service
sudo systemctl daemon-reload
sudo systemctl enable <appname>.service
sudo systemctl start <appname>.service

 

Step 15: Validate Deployment
# Check service status
sudo systemctl status <appname>.service

# Check if Gunicorn is listening
sudo netstat -tulpn | grep :8000

# Check Nginx status
sudo systemctl status nginx

# Test application endpoint
curl http://localhost:8000
curl http://<domain.com>

# Check logs for errors
sudo tail -f /var/log/<appname>/gunicorn-error.log
sudo tail -f /var/log/nginx/<appname>-error.log

Service Management Commands:
# Start service
sudo systemctl start <appname>.service

# Stop service
sudo systemctl stop <appname>.service

# Restart service
sudo systemctl restart <appname>.service

# Reload service (graceful restart)
sudo systemctl reload <appname>.service

# View service status
sudo systemctl status <appname>.service

# View service logs
sudo journalctl -u <appname>.service -f

Log File Paths (Production):
• Gunicorn access logs: /var/log/<appname>/gunicorn-access.log
• Gunicorn error logs: /var/log/<appname>/gunicorn-error.log
• Nginx access logs: /var/log/nginx/<appname>-access.log
• Nginx error logs: /var/log/nginx/<appname>-error.log
• Application logs: /var/log/<appname>/app.log (if configured)
• Systemd logs: sudo journalctl -u <appname>.service

6. Post-Deployment Checklist

☐ Application responds on expected URL/port
☐ Health check endpoint returns 200 OK
☐ Database connection successful


☐ Static files loading correctly
☐ All environment variables properly set
☐ Gunicorn service running and enabled
☐ Nginx service running and configured correctly
☐ SSL certificate installed (if HTTPS required)
☐ Firewall rules configured (if applicable)
☐ Log files being written correctly
☐ No errors in Gunicorn/Nginx error logs
☐ Application logs showing normal operation
☐ DNS records pointing to correct server
☐ Monitoring/alerting configured (if applicable)

7. Common Issues & Fixes

Issue: Service fails to start with "Module not found" error
Cause: Dependencies not installed or wrong virtual environment
Fix:
  sudo -u <appname> -i
  source /var/www/<appname>/venv/bin/activate
  pip install -r /var/www/<appname>/app/requirements.txt
  exit
  sudo systemctl restart <appname>.service

Issue: 502 Bad Gateway error
Cause: Gunicorn not running or wrong port binding
Fix:
  sudo systemctl status <appname>.service
  sudo journalctl -u <appname>.service -n 50
  # Check if port 8000 is in use
  sudo netstat -tulpn | grep :8000
  # Restart service
  sudo systemctl restart <appname>.service

Issue: Permission denied errors
Cause: Incorrect file ownership or permissions
Fix:
  sudo chown -R <appname>:www-data /var/www/<appname>
  sudo chmod -R 755 /var/www/<appname>
  sudo chmod 644 /var/www/<appname>/app/.env.prod
  sudo systemctl restart <appname>.service

 

Issue: Database connection errors
Cause: Incorrect DATABASE_URL or database not accessible
Fix:
  # Verify .env.prod has correct DATABASE_URL
  sudo -u <appname> cat /var/www/<appname>/app/.env.prod | grep DATABASE_URL
  # Test database connectivity
  psql <database-url>  # for PostgreSQL
  mysql -h <host> -u <user> -p  # for MySQL
  # Check firewall rules if database is remote
  sudo ufw status

Issue: Static files not loading (404)
Cause: Nginx not configured to serve static files or wrong path
Fix:
  # Verify static files location
  ls -la /var/www/<appname>/app/static/
  # Check Nginx configuration
  sudo nginx -t
  # Ensure Nginx config has correct alias path
  sudo nano /etc/nginx/sites-available/<appname>
  sudo systemctl reload nginx

Issue: Environment variables not loaded
Cause: EnvironmentFile not specified or wrong path in systemd service
Fix:
  # Verify systemd service file
  sudo nano /etc/systemd/system/<appname>.service
  # Ensure line exists: EnvironmentFile=/var/www/<appname>/app/.env.prod
  sudo systemctl daemon-reload
  sudo systemctl restart <appname>.service

8. Update Procedure

This procedure updates the application code without downtime.

Step 1: Access Server
ssh devops@prod-server.bontonsoftwares.com

Step 2: Switch to Application User
sudo -u <appname> -i
cd /var/www/<appname>/app

Step 3: Backup Current Version
git rev-parse HEAD > /var/www/<appname>/last_working_commit.txt
cp .env.prod .env.prod.backup

 

Step 4: Pull Latest Code
git fetch origin
git pull origin main
# OR for specific version/tag:
# git checkout v1.2.0

Step 5: Update Dependencies
source /var/www/<appname>/venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt

Step 6: Run Database Migrations (if any)
export FLASK_APP=wsgi.py
flask db upgrade
# OR
python manage.py db upgrade

Step 7: Check Configuration
# Review any new environment variables required
cat .env.prod
# Add any new variables if needed
exit  # exit from <appname> user

Step 8: Reload Application Service
sudo systemctl reload <appname>.service
# OR for hard restart:
# sudo systemctl restart <appname>.service

Step 9: Verify Update
# Check service status
sudo systemctl status <appname>.service

# Test application
curl http://localhost:8000
curl http://<domain.com>

# Monitor logs for errors
sudo tail -f /var/log/<appname>/gunicorn-error.log

Step 10: Monitor for Issues
# Watch logs for 5-10 minutes
sudo journalctl -u <appname>.service -f

# If issues occur, proceed to rollback (Section 9)

9. Rollback Procedure

Use this procedure to revert to the last working version if the update fails.

Step 1: Access Server
ssh devops@prod-server.bontonsoftwares.com

Step 2: Switch to Application User
sudo -u <appname> -i
cd /var/www/<appname>/app

 

Step 3: Retrieve Last Working Commit
LAST_COMMIT=$(cat /var/www/<appname>/last_working_commit.txt)
echo "Rolling back to commit: $LAST_COMMIT"

Step 4: Revert to Last Working Version
git checkout $LAST_COMMIT
# OR if you know the tag:
# git checkout v1.1.0

Step 5: Restore Environment Configuration (if needed)
cp .env.prod.backup .env.prod

Step 6: Reinstall Dependencies
source /var/www/<appname>/venv/bin/activate
pip install -r requirements.txt

Step 7: Rollback Database Migrations (if necessary)
# CAUTION: Only if new migrations were applied and are causing issues
export FLASK_APP=wsgi.py
flask db downgrade
# OR specify target revision:
# flask db downgrade <revision-id>
exit  # exit from <appname> user

Step 8: Restart Application Service
sudo systemctl restart <appname>.service

Step 9: Verify Rollback
# Check service status
sudo systemctl status <appname>.service

# Test application
curl http://localhost:8000
curl http://<domain.com>

# Verify logs show no errors
sudo tail -f /var/log/<appname>/gunicorn-error.log
sudo journalctl -u <appname>.service -n 50

Step 10: Confirm Stability
# Monitor for 5-10 minutes to ensure application is stable
sudo journalctl -u <appname>.service -f

Step 11: Document Incident
# Record what went wrong, why rollback was needed
# Update deployment notes for next attempt

---

Document Version: 1.0
Last Updated: 2025-11-21
Maintained by: DevOps Team, Bonton Softwares