Project: Production Deployment
Introduction
This project takes a finished Flask app (blog, API, or todo) from local development to a production server using Gunicorn, Nginx, environment-based config, migrations, logging, and optional Docker. It ties together deploy, security, and Linux workflow chapters into one repeatable checklist.
Prerequisites
- A working Flask app with
create_appandwsgi.py - Deploying With Gunicorn and Nginx
- Flask Security Best Practices
- Flask Docker and CI (optional Docker path)
- Linux VPS or cloud instance—developer workflow on Linux
Project Goals
- App runs under Gunicorn via systemd
- Nginx reverse proxy with static files
- Secrets in
/etc/flask-app/env, not in repo flask db upgradeon deploy/healthfor monitoring- Rotating application logs
Step 1: Production Config
app/config.py:
class ProductionConfig(Config):
DEBUG = False
TESTING = False
SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
SECRET_KEY = os.environ["SECRET_KEY"]
SESSION_COOKIE_SECURE = True
REMEMBER_COOKIE_SECURE = Truewsgi.py:
from app import create_app
app = create_app("production")Verify locally with production env (careful with real DB):
export FLASK_ENV=production
export SECRET_KEY="local-prod-test"
export DATABASE_URL="sqlite:///prod-test.db"
gunicorn -w 2 -b 127.0.0.1:8000 wsgi:app
curl http://127.0.0.1:8000/healthStep 2: Server Preparation
On Ubuntu VPS:
sudo apt update
sudo apt install -y python3-venv python3-pip nginx
sudo useradd --system --home /var/www/flask-app --shell /bin/bash www-data || true
sudo mkdir -p /var/www/flask-app
sudo chown www-data:www-data /var/www/flask-appClone or rsync project:
sudo -u www-data git clone https://github.com/you/flask-app.git /var/www/flask-app
cd /var/www/flask-app
sudo -u www-data python3 -m venv .venv
sudo -u www-data .venv/bin/pip install -r requirements.txt gunicornStep 3: Environment File
/etc/flask-app/env (root only, mode 600):
SECRET_KEY=long-random-hex-from-secrets-module
DATABASE_URL=mysql+pymysql://flask:STRONG_PASS@127.0.0.1/flask_app?charset=utf8mb4
FLASK_ENV=productionCreate MySQL database and user—MySQL on Linux.
Step 4: Migrations on Server
cd /var/www/flask-app
sudo -u www-data bash -c 'set -a; source /etc/flask-app/env; set +a; .venv/bin/flask db upgrade'Automate this in deploy script before restarting Gunicorn.
Step 5: systemd Unit
/etc/systemd/system/flask-app.service:
Prepare log dir:
sudo mkdir -p /var/log/flask-app
sudo chown www-data:www-data /var/log/flask-app
sudo systemctl daemon-reload
sudo systemctl enable --now flask-app
sudo systemctl status flask-appStep 6: Nginx Site
/etc/nginx/sites-available/flask-app:
Enable and test:
sudo ln -s /etc/nginx/sites-available/flask-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxHTTPS with Certbot—HTTPS and Let's Encrypt.
Proxy patterns match Nginx proxy for Spring Boot and Node.
Step 7: Application Logging
In create_app:
Step 8: Deploy Script
scripts/deploy.sh on server:
#!/bin/bash
set -euo pipefail
APP_DIR=/var/www/flask-app
cd "$APP_DIR"
sudo -u www-data git pull
sudo -u www-data .venv/bin/pip install -r requirements.txt
set -a; source /etc/flask-app/env; set +a
sudo -u www-data .venv/bin/flask db upgrade
sudo systemctl restart flask-app
curl -sf http://127.0.0.1:8000/health
echo "Deploy OK"Run after CI passes on main—link Git and CI/CD.
Step 9: Docker Path (Alternative)
If you prefer containers, use docker compose up -d from Docker and CI chapter and put Nginx on the host in front of port 8000.
Step 10: Post-Deploy Verification
curl -I http://todo.example.com/health
curl -I http://todo.example.com/static/css/style.css
journalctl -u flask-app -n 50 --no-pagerBrowser: register, login, core feature works over HTTPS.
Production Checklist
-
DEBUG=False - Strong
SECRET_KEYand DB password - Gunicorn bound to 127.0.0.1 only
- Nginx
proxy_set_headerconfigured - HTTPS enabled; secure cookies
- Migrations applied
- Logs rotating; disk space monitored
- Firewall allows 80/443, blocks 8000 publicly
- Backups for MySQL—backup and restore
Rollback Plan
cd /var/www/flask-app
sudo -u www-data git checkout PREVIOUS_SHA
sudo -u www-data .venv/bin/pip install -r requirements.txt
flask db downgrade # if migration incompatible—plan carefully
sudo systemctl restart flask-appPrefer forward-fix migrations over downgrade in production.
FAQ
502 after deploy?
journalctl -u flask-app—import errors, missing env var, or wrong WorkingDirectory.
Static CSS 404?
Check Nginx alias path matches deployed app/static/.
Permission denied on logs?
chown www-data on log directories.
Zero-downtime deploy?
Two Gunicorn pools + load balancer, or blue/green—advanced; restart causes brief blip.
Environment on shared hosting?
Use host panel env vars; same 12-factor keys.
Monitor uptime?
External ping on /health; alert on non-200.
Git pull as www-data?
SSH deploy key or CI rsync artifact—avoid root-owned files in app dir.