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
No comments to display
No comments to display