Deployment
So you've built your Flask app and it works great on your machine. Now what? Getting it online so other people can use it is called deployment, and there are several layers to understand. Don't worry โ it's not as scary as it seems.
Why the Development Server Isn't Enough
When you run flask run, Flask starts a development server. It's convenient for testing but has serious limitations โ it's single-threaded, doesn't handle errors gracefully, and isn't secure for production. You need a proper WSGI server.
Gunicorn: The Production Server
Gunicorn (Green Unicorn) is a production WSGI server that handles multiple requests simultaneously. It's the standard for running Flask in production.
# Install gunicorn
pip install gunicorn
# Run your app with gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 app:app
# -w 4 means 4 worker processes
# -b binds to all interfaces on port 8000
# app:app means the 'app' variable in the 'app' module
Gunicorn spawns multiple worker processes to handle concurrent requests. For most apps, 2-4 workers per CPU core is a good starting point.
Nginx as Reverse Proxy
Nginx sits in front of Gunicorn and handles things like serving static files, SSL encryption, load balancing, and request buffering. It's the public face of your server.
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name myapp.com;
location /static {
alias /var/www/myapp/static;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Nginx serves static files directly (much faster than Python) and forwards dynamic requests to Gunicorn. This is the standard production setup.
Environment Variables in Production
Never hardcode secrets in production. Use environment variables to keep your configuration separate from your code.
import os
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')
app.config['DEBUG'] = False # Always False in production
Docker Basics
Docker packages your app with all its dependencies into a container. It runs the same everywhere โ your machine, a server, the cloud.
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]
Build and run with docker build -t myapp . then docker run -p 8000:8000 -e SECRET_KEY=mysecret myapp.
Platform Deployment
Platforms like Heroku, Railway, and Render handle much of the deployment complexity. You push your code, and they handle the rest โ building, running, scaling, and SSL. Create a Procfile and a requirements.txt, push to their platform, and your app is live.
# Procfile (for Heroku/Railway)
web: gunicorn app:app
# runtime.txt
python-3.11.4
Database Migrations in Production
Running db.create_all() in production is risky. Use Flask-Migrate to apply changes safely. Run flask db upgrade as part of your deployment process so schema changes are applied cleanly.
Why Production Is Different
The development server prioritizes convenience over everything else. Production needs security, performance, reliability, and monitoring. A proper deployment stack โ Gunicorn behind Nginx, with environment variables and database migrations โ gives you all of that. It's not just about making your app accessible; it's about making it trustworthy.