Preparing Your App for Production
A production entry point
wsgi.py:
from app import create_app
app = create_app()
Run with Gunicorn instead of the dev server:
pip install gunicorn
gunicorn wsgi:app --workers 2 --threads 4 --bind 0.0.0.0:$PORT
Keep Gunicorn settings in gunicorn.conf.py reading environment variables, so scaling up is a settings change:
import os
workers = int(os.environ.get("WEB_CONCURRENCY", 2))
threads = int(os.environ.get("GUNICORN_THREADS", 4))
bind = f"0.0.0.0:{os.environ.get('PORT', '8000')}"
timeout = int(os.environ.get("GUNICORN_TIMEOUT", 60))
A rough starting point: memory decides workers. A small Flask app uses roughly 80–150 MB per worker; on a 512 MB instance use 1–2 workers with threads.
Configuration by environment
import os
class Config:
SECRET_KEY = os.environ["SECRET_KEY"] # crash early if missing
SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
Generate secrets properly: python -c "import secrets; print(secrets.token_hex(32))".
Some platforms give postgres:// URLs; SQLAlchemy needs postgresql://:
url = os.environ["DATABASE_URL"].replace("postgres://", "postgresql://", 1)
Behind a proxy
Platforms terminate HTTPS at a proxy in front of your app. Tell Flask so redirects use https and the client IP is right:
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)
Static files
Small apps can serve static files with WhiteNoise or let the platform do it; set long cache headers on versioned files. User uploads should go to object storage (S3, Cloudflare R2) or a persistent disk.
Health check endpoint
@app.get("/api/health")
def health():
db.session.execute(text("SELECT 1"))
return {"status": "ok"}
The platform calls it to know when a new deploy is ready, and uptime monitors use it to alert you.
Security headers
Add them with a small after_request hook or Flask-Talisman: Strict-Transport-Security, X-Content-Type-Options: nosniff, Referrer-Policy, a Content-Security-Policy and X-Frame-Options: DENY (or CSP frame-ancestors).
Pre-deploy checklist
- [ ] Debug off;
SECRET_KEYfrom the environment - [ ]
requirements.txtpinned; app boots from a clean virtual environment - [ ] Migrations run as part of the build or release step
- [ ] Health check route
- [ ] Logs go to stdout
- [ ] No secrets in Git (
git grep -i secret)
Check yourself
- Why not use
flask runin production? - What does ProxyFix fix?
- What should a health check verify?
Your progress is saved in this browser only.
Submit for review (10 points)