Flask e Django em produção com Gunicorn e Nginx

Flask e Django têm servidores de desenvolvimento embutidos que não são adequados para produção, não suportam concorrência real. O stack correto para produção é: Gunicorn como servidor WSGI (processa requisições em paralelo), Nginx como proxy reverso (gerencia TLS, compressão, assets estáticos) e Docker para empacotamento.

Por que não usar o servidor embutido em produção

Flask e Django têm servidores de desenvolvimento com avisos explícitos, entenda as limitações antes de escolher o substituto:

bash
# Servidor de desenvolvimento (NUNCA em produção):
flask run          # Warning: "Do not use the development server in a production environment"
python manage.py runserver  # Django: "This is not a production server!"

# Problemas do servidor de desenvolvimento:
# - Single-threaded: uma requisição por vez
# - Sem gerenciamento de processos: crash = downtime
# - Sem TLS nativo
# - Sem compressão de resposta

# Stack de produção correto:
# Cliente → Nginx (TLS, assets, compressão) → Gunicorn (workers) → Flask/Django app

Configurar Gunicorn para produção

Gunicorn em produção precisa de configuração explícita de workers, timeout e bind:

python
# gunicorn.conf.py (arquivo de configuração)
import multiprocessing

# Número de workers: (2 × CPUs) + 1
workers = multiprocessing.cpu_count() * 2 + 1

# Tipo de worker
worker_class = "gthread"   # threads por worker
threads = 4                # 4 threads por worker (workers × threads = concorrência total)

# Bind
bind = "0.0.0.0:8000"

# Timeouts
timeout = 120              # matar worker que não responde em 120s
graceful_timeout = 30      # tempo para worker terminar antes de ser forçado
keepalive = 5              # tempo keepalive em segundos

# Logs
accesslog = "-"            # stdout
errorlog = "-"             # stderr
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Segurança
limit_request_line = 4094
limit_request_fields = 100

Dockerfile para Flask/Django com Gunicorn

Imagem de produção com Gunicorn configurado:

dockerfile
# Dockerfile
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq-dev gcc \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# Coletar arquivos estáticos do Django
RUN python manage.py collectstatic --noinput || true

RUN useradd --create-home appuser && chown -R appuser /app
USER appuser

EXPOSE 8000

# Gunicorn com arquivo de config
CMD ["gunicorn", "--config", "gunicorn.conf.py", "config.wsgi:application"]
# Para Flask: CMD ["gunicorn", "--config", "gunicorn.conf.py", "app:app"]

Nginx para servir arquivos estáticos do Django

Para Django, o Nginx deve servir os arquivos estáticos diretamente, sem passar pelo Gunicorn:

nginx
# nginx.conf para Django/Flask
server {
    listen 443 ssl;
    http2 on;
    server_name app.seudominio.com.br;

    ssl_certificate /etc/letsencrypt/live/app.seudominio.com.br/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.seudominio.com.br/privkey.pem;

    # Servir arquivos estáticos diretamente (Django collectstatic)
    location /static/ {
        alias /app/staticfiles/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # Servir arquivos de upload
    location /media/ {
        alias /app/media/;
        expires 30d;
    }

    # Proxy para Gunicorn
    location / {
        proxy_pass http://gunicorn: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;
        client_max_body_size 50M;
    }
}

Variáveis de ambiente e configuração Django

django-environ ou python-decouple separam configuração de código. Configure DEBUG=False e ALLOWED_HOSTS para produção:

python
# settings.py (usando django-environ)
import environ

env = environ.Env(DEBUG=(bool, False))
environ.Env.read_env()

SECRET_KEY = env("SECRET_KEY")
DEBUG = env("DEBUG")
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")

DATABASES = {
    "default": env.db()  # parseia DATABASE_URL automaticamente
}

# Configurações de produção (DEBUG=False)
SECURE_SSL_REDIRECT = not DEBUG
SESSION_COOKIE_SECURE = not DEBUG
CSRF_COOKIE_SECURE = not DEBUG
SECURE_HSTS_SECONDS = 31536000 if not DEBUG else 0
SECURE_HSTS_INCLUDE_SUBDOMAINS = not DEBUG

# .env (não commitado)
SECRET_KEY=chave_secreta_muito_longa
DEBUG=false
ALLOWED_HOSTS=app.seudominio.com.br,www.seudominio.com.br
DATABASE_URL=postgresql://user:pass@postgres:5432/db

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Gunicorn: mais simples de configurar, documentação excelente, padrão da maioria dos tutoriais Django/Flask. uWSGI: mais recursos (cache, protocol buffering, emperor mode para múltiplos apps), mas configuração mais complexa. Para 99% dos casos: Gunicorn. uWSGI faz sentido em infraestruturas muito grandes ou que precisam de features específicas como uwsgi protocol.

Envie SIGHUP para o processo master: kill -HUP $(cat gunicorn.pid). O Gunicorn faz graceful reload: aguarda workers terminarem as requisições atuais, inicia novos workers com o código novo, descarta os antigos. No Docker: docker compose exec gunicorn kill -HUP 1 (PID 1 é o processo master).

Não. Django Channels é ASGI, não WSGI, incompatível com Gunicorn padrão. Use Daphne (servidor ASGI do Django Channels) ou Uvicorn como servidor. É possível ter um Gunicorn para HTTP e um Daphne para WebSocket separados, com Nginx roteando por path.

Sentry é a ferramenta padrão: pip install sentry-sdk e SENTRY_DSN no .env. Para Django: sentry_sdk.init(dsn=...) + DjangoIntegration(). Para Flask: sentry_sdk.init(dsn=...) + FlaskIntegration(). Captura automaticamente exceções com stack trace, request data e contexto de usuário. O plano gratuito do Sentry é suficiente para a maioria dos projetos.

Flask ainda é relevante para: apps legadas já em Flask (custo de migração alto), equipes familiarizadas com o ecossistema Flask, projetos que precisam de Flask-specific extensions (Flask-Login, Flask-Admin). Para novos projetos de API em 2025: FastAPI tem vantagens claras (async nativo, tipagem, docs automático). Para apps web completas com admin: Django ainda domina.