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

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 upgrade on deploy
  • /health for monitoring
  • Rotating application logs

Step 1: Production Config

app/config.py:

python
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 = True

wsgi.py:

python
from app import create_app
 
app = create_app("production")

Verify locally with production env (careful with real DB):

bash
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/health

Step 2: Server Preparation

On Ubuntu VPS:

bash
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-app

Clone or rsync project:

bash
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 gunicorn

Step 3: Environment File

/etc/flask-app/env (root only, mode 600):

bash
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=production

Create MySQL database and user—MySQL on Linux.

Step 4: Migrations on Server

bash
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:

bash
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-app

Step 6: Nginx Site

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

Enable and test:

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

HTTPS 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:

bash
#!/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

bash
curl -I http://todo.example.com/health
curl -I http://todo.example.com/static/css/style.css
journalctl -u flask-app -n 50 --no-pager

Browser: register, login, core feature works over HTTPS.

Production Checklist

  • DEBUG=False
  • Strong SECRET_KEY and DB password
  • Gunicorn bound to 127.0.0.1 only
  • Nginx proxy_set_header configured
  • 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

bash
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-app

Prefer 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.