Skip to main content

Flask: Deploying with Gunicorn and Nginx

Flask: Deploying with Gunicorn and Nginx

Deploying a Flask application with Gunicorn and Nginx provides a robust, production-ready setup for serving web applications efficiently and securely. Flask, built on a lightweight core with Jinja2 Templating and Werkzeug WSGI, pairs seamlessly with Gunicorn (a Python WSGI server) for application serving and Nginx (a high-performance web server) for reverse proxying, static file serving, and load balancing. This tutorial covers Flask deploying with Gunicorn and Nginx, providing a step-by-step guide for deployment on a Linux server (e.g., Ubuntu), including configuration, security, and best practices.


01. Why Use Gunicorn and Nginx?

Gunicorn handles the execution of Flask applications, processing dynamic requests efficiently with multiple worker processes, while Nginx acts as a reverse proxy, serving static files, managing SSL/TLS, and balancing load. This combination leverages Flask’s compatibility with Werkzeug WSGI for request handling and Jinja2 Templating for rendering, ensuring high performance, scalability, and security for production environments.

Example: Basic Flask App

# app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello, Gunicorn and Nginx!'

if __name__ == '__main__':
    app.run()

Explanation:

  • A minimal Flask app to deploy with Gunicorn and Nginx.
  • The development server (app.run()) is not used in production.

02. Prerequisites

Before starting, ensure the following are available:

  • Linux Server: Ubuntu 20.04 or later (e.g., AWS EC2, DigitalOcean, or local VM).
  • Python 3.10+: Installed on the server (sudo apt install python3 python3-pip python3-venv).
  • Git: For cloning the app repository (sudo apt install git).
  • Domain (Optional): For SSL setup with Let’s Encrypt.
  • SSH Access: To configure the server.

03. Step-by-Step Deployment Guide

This guide assumes deployment on an Ubuntu 22.04 server. Adjust commands for other Linux distributions as needed.

3.1 Set Up the Server

Example: Initial Server Setup

# Update system and install dependencies
sudo apt update
sudo apt install -y python3 python3-pip python3-venv git nginx

# Create a non-root user (optional, for security)
sudo adduser --gecos "" flaskuser
sudo usermod -aG sudo flaskuser
su - flaskuser

Explanation:

  • Installs Python, Git, and Nginx.
  • Creates a non-root user for security (recommended but optional).

3.2 Clone the Flask Application

Example: Clone and Set Up App

# Clone repository (replace with your repo URL)
git clone https://github.com/yourusername/flask-app.git
cd flask-app

# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate

# Install dependencies
pip install flask gunicorn
pip freeze > requirements.txt

requirements.txt (Generated):

Flask==3.0.3
gunicorn==22.0.0
Jinja2==3.1.4
Werkzeug==3.0.4
...

Explanation:

  • Clones the Flask app from a Git repository.
  • Sets up a virtual environment and installs gunicorn for production.

3.3 Configure Gunicorn

Example: Test Gunicorn

# Test Gunicorn (inside flask-app directory)
gunicorn --workers 3 --bind 0.0.0.0:8000 app:app

Updated app.py with Environment Variables:

# app.py
from flask import Flask
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')

@app.route('/')
def index():
    return 'Hello, Gunicorn and Nginx!'

if __name__ == '__main__':
    app.run()

Explanation:

  • gunicorn app:app: Runs the app from app.py with the Flask instance app.
  • --workers 3: Uses 3 worker processes (adjust based on CPU cores, typically 2*CPU + 1).
  • Test at http://<server-ip>:8000. Stop with Ctrl+C.

3.4 Set Up Gunicorn as a Service

Example: Systemd Service for Gunicorn

# Create systemd service file
sudo nano /etc/systemd/system/flaskapp.service

/etc/systemd/system/flaskapp.service:

[Unit]
Description=Gunicorn instance for Flask app
After=network.target

[Service]
User=flaskuser
Group=www-data
WorkingDirectory=/home/flaskuser/flask-app
Environment="SECRET_KEY=my-secure-key"
ExecStart=/home/flaskuser/flask-app/venv/bin/gunicorn --workers 3 --bind unix:/home/flaskuser/flask-app/flaskapp.sock app:app

[Install]
WantedBy=multi-user.target

Enable and Start Service:

sudo systemctl start flaskapp
sudo systemctl enable flaskapp
sudo systemctl status flaskapp

Explanation:

  • Creates a systemd service to run Gunicorn automatically.
  • Uses a Unix socket (flaskapp.sock) for communication with Nginx.
  • Sets environment variables directly in the service file (alternatively, use a .env file).

3.5 Configure Nginx

Example: Nginx Configuration

# Create Nginx config
sudo nano /etc/nginx/sites-available/flaskapp

/etc/nginx/sites-available/flaskapp:

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;  # Replace with your domain or server IP

    location / {
        proxy_pass http://unix:/home/flaskuser/flask-app/flaskapp.sock;
        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;
    }

    location /static/ {
        alias /home/flaskuser/flask-app/static/;
    }
}

Enable Nginx Config:

sudo ln -s /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled
sudo nginx -t  # Test config
sudo systemctl restart nginx

Explanation:

  • Nginx proxies requests to Gunicorn via the Unix socket.
  • Serves static files directly from the static/ folder.
  • Replace your-domain.com with your domain or server IP.

3.6 Secure with SSL Using Let’s Encrypt

Example: SSL Setup

# Install Certbot
sudo apt install -y certbot python3-certbot-nginx

# Obtain SSL certificate
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

Updated Nginx Config (Auto-Updated by Certbot):

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name your-domain.com www.your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    location / {
        proxy_pass http://unix:/home/flaskuser/flask-app/flaskapp.sock;
        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;
    }

    location /static/ {
        alias /home/flaskuser/flask-app/static/;
    }
}

Restart Nginx:

sudo systemctl restart nginx

Explanation:

  • Certbot automates SSL certificate issuance and Nginx configuration.
  • Redirects HTTP to HTTPS for security.

3.7 Common Deployment Issues

Example: Gunicorn Socket Permission Error

nginx: [emerg] connect() to unix:/home/flaskuser/flask-app/flaskapp.sock failed (13: Permission denied)

Solution:

sudo chown www-data:www-data /home/flaskuser/flask-app/flaskapp.sock
sudo chmod 660 /home/flaskuser/flask-app/flaskapp.sock
sudo systemctl restart flaskapp
sudo systemctl restart nginx

Explanation:

  • Ensures Nginx (www-data) can access the Gunicorn socket.
  • Check logs with sudo journalctl -u flaskapp or sudo tail -f /var/log/nginx/error.log.

04. Best Practices

  • Environment Variables: Use .env with python-dotenv or systemd for sensitive data.
  • .gitignore: Exclude venv/, .env, and *.pyc.
  • # .gitignore
    venv/
    .env
    *.pyc
    
  • Logging: Configure Gunicorn logging in flaskapp.service (e.g., --access-logfile -).
  • Security: Use SSL, set SESSION_COOKIE_SECURE=True, and restrict server access (e.g., firewall with UFW).
  • Scaling: Adjust Gunicorn workers based on CPU cores and memory.

4.1 Comprehensive Example

Example: Full Deployment with Database

# app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv
import os

load_dotenv()

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///app.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SESSION_COOKIE_SECURE'] = True
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)

@app.route('/')
def index():
    users = User.query.all()
    return f'Users: {[user.username for user in users]}'

if __name__ == '__main__':
    app.run()

.env File:

SECRET_KEY=my-secure-key
DATABASE_URL=sqlite:///app.db

requirements.txt:

Flask==3.0.3
gunicorn==22.0.0
flask-sqlalchemy==3.1.1
python-dotenv==1.0.1

flaskapp.service:

[Unit]
Description=Gunicorn instance for Flask app
After=network.target

[Service]
User=flaskuser
Group=www-data
WorkingDirectory=/home/flaskuser/flask-app
EnvironmentFile=/home/flaskuser/flask-app/.env
ExecStart=/home/flaskuser/flask-app/venv/bin/gunicorn --workers 3 --bind unix:/home/flaskuser/flask-app/flaskapp.sock app:app

[Install]
WantedBy=multi-user.target

Deployment Commands:

sudo systemctl start flaskapp
sudo systemctl enable flaskapp
sudo ln -s /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled
sudo nginx -t
sudo systemctl restart nginx
sudo certbot --nginx -d your-domain.com

Explanation:

  • Integrates SQLAlchemy, environment variables, and SSL.
  • Runs securely with Gunicorn and Nginx.

05. Common Use Cases

5.1 Deploying a Web Application

Example: Web App with Static Files

# app.py
from flask import Flask, render_template
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')

@app.route('/')
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run()

templates/index.html:

<!DOCTYPE html>
<html>
<head><title>Flask App</title></head>
<body><h1>Welcome!</h1><img src="/static/logo.png"></body>
</html>

Explanation:

  • Nginx serves /static/logo.png efficiently.
  • Uses Jinja2 Templating for dynamic rendering.

5.2 Deploying an API

Example: Flask API

# app.py
from flask import Flask, jsonify
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')

@app.route('/api/data')
def data():
    return jsonify({'message': 'API with Gunicorn and Nginx'})

if __name__ == '__main__':
    app.run()

Explanation:

  • Deploys a RESTful API with Gunicorn handling dynamic requests.
  • Nginx proxies API requests securely over HTTPS.

Conclusion

Deploying Flask applications with Gunicorn and Nginx, powered by Jinja2 Templating and Werkzeug WSGI, provides a high-performance, secure, and scalable production environment. Key takeaways:

  • Use Gunicorn for WSGI serving and Nginx for reverse proxying and static files.
  • Secure with SSL via Let’s Encrypt and environment variables.
  • Manage Gunicorn with systemd for reliability.
  • Troubleshoot using logs and ensure proper permissions.

This setup ensures Flask applications are production-ready, handling high traffic and securing user data effectively!

Comments