Deploying Python Web Apps
Deploying a Python web application to production requires several steps beyond development. Your app needs a WSGI server, a reverse proxy, SSL certificates, and proper configuration. This tutorial covers each component and shows you how to deploy with confidence.
Choosing a Hosting Platform
Your deployment choice depends on budget, scalability needs, and operational complexity.
Virtual Private Servers (VPS) VPS providers give you full control over the server. You manage everything: OS, software, security updates. Popular options include DigitalOcean, Linode, Hetzner, and AWS EC2. This approach offers maximum flexibility but requires sysadmin skills.
Platform as a Service (PaaS) Platforms like Render, Heroku, Railway, and Fly.io abstract server management. You push code, they handle the infrastructure. Simpler to start but more expensive at scale and less control over the environment.
Container Platforms Docker + Kubernetes or Docker Swarm gives you consistency across environments. Containers package your app with all dependencies. This approach scales well but has a learning curve.
For this tutorial, we assume a VPS with Ubuntu. The concepts apply elsewhere with minor adjustments.
Preparing Your Application for Production
Before deploying, update your configuration for production use.
Environment Variables
Never hardcode secrets in your code. Use environment variables for sensitive data.
import os
# Instead of: API_KEY = "secret123"
API_KEY = os.environ.get("API_KEY")
# With a default for development
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
Create a .env file for local development (add it to .gitignore):
# .env
DEBUG=false
SECRET_KEY=your-secure-random-secret
DATABASE_URL=postgresql://user:pass@localhost/mydb
Requirements or Dependencies File
Pin your dependencies to specific versions:
pip freeze > requirements.txt
Or use pyproject.toml with modern tools like Poetry or uv:
[project]
dependencies = [
"flask>=2.0,<3.0",
"gunicorn>=20.0,<21.0",
]
Directory Structure
A typical production structure:
myapp/
├── app/
│ ├── __init__.py
│ └── routes.py
├── requirements.txt
├── .env
├── gunicorn.conf.py
└── start.sh
Using Gunicorn
Flask’s development server (app.run()) is not suitable for production. It handles one request at a time and isn’t secure.
Gunicorn is a battle-tested WSGI server. Install it:
pip install gunicorn
Run it:
gunicorn -w 4 -b 127.0.0.1:8000 "app:create_app()"
-w 4: Four worker processes (typically 2-4 × CPU cores)-b: Bind address and port"app:create_app()": Your application factory
Gunicorn Configuration
Create gunicorn.conf.py for production settings:
import multiprocessing
bind = "127.0.0.1:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
timeout = 30
keepalive = 2
# Logging
accesslog = "-"
errorlog = "-"
loglevel = "info"
# Process naming
proc_name = "myapp"
Test your configuration:
gunicorn -c gunicorn.conf.py "app:create_app()"
Setting Up Nginx as a Reverse Proxy
Nginx sits in front of Gunicorn, handling static files and forwarding dynamic requests.
Install Nginx:
sudo apt update
sudo apt install nginx
Create a site configuration:
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Static files
location /static/ {
alias /path/to/myapp/static/;
}
# Media files
location /media/ {
alias /path/to/myapp/media/;
}
# Proxy to Gunicorn
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;
}
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Securing with SSL/HTTPS
Let’s Encrypt provides free certificates. Install Certbot:
sudo apt install certbot python3-certbot-nginx
Obtain and install the certificate:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot automatically configures HTTPS and sets up auto-renewal. Test renewal:
sudo certbot renew --dry-run
Your site now serves over HTTPS with automatic certificate renewal.
Systemd Service
Create a systemd service to manage your application:
# /etc/systemd/system/myapp.service
[Unit]
Description=Gunicorn instance for myapp
After=network.target
[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/path/to/myapp
Environment="PATH=/path/to/myapp/venv/bin"
ExecStart=/path/to/myapp/venv/bin/gunicorn -c gunicorn.conf.py "app:create_app()"
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl status myapp
Basic Deployment Automation
Manual deployments are error-prone. Use a simple deployment script:
#!/bin/bash
# deploy.sh
set -e
echo "Pulling latest code..."
cd /path/to/myapp
git pull origin main
echo "Installing dependencies..."
source venv/bin/activate
pip install -r requirements.txt
echo "Running migrations..."
flask db upgrade # or your migration command
echo "Restarting application..."
sudo systemctl restart myapp
echo "Deployment complete!"
Make it executable:
chmod +x deploy.sh
For more advanced deployments, consider tools like Fabric, Invoke, or Ansible.
Monitoring Basics
After deployment, monitor your application.
Check Application Status
sudo systemctl status myapp
journalctl -u myapp -f # Follow logs
Health Check Endpoint
Add a simple health check to your app:
@app.route("/health")
def health():
return {"status": "ok"}
Nginx Status
Enable Nginx status page for monitoring:
location /nginx-status {
stub_status on;
allow 127.0.0.1;
deny all;
}
Conclusion
You now understand the full deployment pipeline: WSGI server (Gunicorn), reverse proxy (Nginx), SSL (Let’s Encrypt), process management (systemd), and basic automation. These components work together to serve your Python application reliably in production.
For smaller projects, PaaS options like Render or Railway eliminate much of this complexity. As your needs grow, the VPS approach gives you control and scalability. Start simple, iterate, and learn from each deployment.