Como hospedar FastAPI, Flask ou Django

Para hospedar FastAPI, Flask ou Django, use um servidor de produção em vez do servidor de desenvolvimento: Uvicorn para FastAPI, Gunicorn para Flask e Django. Escute em 0.0.0.0 e leia segredos de variáveis de ambiente.

Atualizado em 6 min de leitura

Objetivos de aprendizado

Depois de ler este artigo, você vai conseguir:

  • Escolher o servidor de produção certo para cada framework
  • Preparar e iniciar um app FastAPI, Flask ou Django fora do seu computador
  • Corrigir os erros mais comuns de um app Python recém-publicado
Nesta página

Os três frameworks trazem um servidor embutido que sobe com um comando, e é aí que mora o erro mais comum: levar esse servidor para produção. Ele foi feito para desenvolver. Para publicar, o app precisa de um servidor de aplicação (Uvicorn ou Gunicorn) que escute na interface certa e leia a configuração do ambiente.

Este guia mostra o mínimo de cada framework, o comando de produção correspondente e os ajustes que o Django exige. O raciocínio geral de publicar uma API está em como hospedar uma API.

RequisiçãoHTTPSProxyda hospedagemHTTPGunicorn ou UvicornFlaskDjangoFastAPIWorker 1seu appWorker 2seu appWorker 3seu appBanco de dados
O proxy repassa a requisição ao Gunicorn ou Uvicorn, que a entrega a um dos workers que rodam o seu app.

Qual servidor de produção cada framework usa?#

A diferença está na interface entre o framework e o servidor:

Framework Interface Servidor de produção Comando típico
FastAPI ASGI Uvicorn (vem com fastapi[standard]) fastapi run ou uvicorn main:app
Flask WSGI Gunicorn gunicorn 'app:app'
Django WSGI (ou ASGI) Gunicorn gunicorn meusite.wsgi

O Gunicorn não roda no Windows (só no WSL). Servidores de produção costumam ser Linux, então isso afeta só quem testa localmente.

Como hospedar um app FastAPI?#

Instale com os extras padrão, que incluem o Uvicorn:

bash
pip install "fastapi[standard]"

main.py:

python
from fastapi import FastAPI

app = FastAPI()


@app.get("/health")
def health():
    return {"status": "ok"}

Em produção, sem --reload (a documentação do FastAPI avisa que ele consome mais recursos e é mais instável):

bash
uvicorn main:app --host 0.0.0.0 --port "${PORT:-8000}" --workers 2

main:app significa "o objeto app do arquivo main.py". Cada worker é um processo separado, e o código de inicialização roda uma vez por worker. O fastapi run main.py é a alternativa curta: também escuta em 0.0.0.0, na porta 8000 por padrão.

Como hospedar um app Flask?#

bash
pip install flask gunicorn

app.py:

python
import os

from flask import Flask

app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]


@app.get("/health")
def health():
    return {"status": "ok"}

Inicie com o Gunicorn:

bash
gunicorn -w 4 -b "0.0.0.0:${PORT:-8000}" 'app:app'

O argumento app:app segue o formato {módulo}:{variável}. Com o padrão application factory, use 'app:create_app()'. O -w define o número de processos, e a documentação do Flask sugere CPU * 2 como ponto de partida. Sem -b, o Gunicorn escuta em 0.0.0.0 na porta da variável PORT, se ela existir; sem PORT, escuta só em 127.0.0.1:8000, e o app parece fora do ar. Passar -b deixa o endereço explícito nos dois casos.

Como hospedar um app Django?#

O Django exige mais ajustes: com DEBUG ligado, ele expõe código e configuração, e em produção ninguém serve os arquivos estáticos por você.

bash
pip install django gunicorn whitenoise

No settings.py, leia do ambiente o que muda entre desenvolvimento e produção:

python
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = os.environ.get("DEBUG") == "1"
ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(",")
CSRF_TRUSTED_ORIGINS = [
    o for o in os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",") if o
]

STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
    "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"
    },
}

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",  # logo após o SecurityMiddleware
    # ... demais middlewares
]

Com ALLOWED_HOSTS=meuapp.exemplo.com e CSRF_TRUSTED_ORIGINS=https://meuapp.exemplo.com, o domínio público fica liberado. O CSRF_TRUSTED_ORIGINS leva o esquema (https://) e vale para requisições POST vindas de outra origem.

Antes de iniciar, reúna os estáticos e rode as migrações:

bash
python manage.py collectstatic --noinput
python manage.py migrate
gunicorn meusite.wsgi -b "0.0.0.0:${PORT:-8000}"

O WhiteNoise serve os arquivos de STATIC_ROOT direto do processo Python, então você dispensa um servidor web só para eles. Por fim, rode python manage.py check --deploy com as configurações de produção: ele aponta o que falta.

Se o proxy da hospedagem termina o HTTPS e repassa HTTP ao Django, o request.is_secure() devolve False. A saída é SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https"), mas a documentação do Django só autoriza isso se o proxy descartar o X-Forwarded-Proto enviado pelo cliente e só o definir em requisições que chegaram por HTTPS. Sem essa garantia, mantenha None.

Como saber se o deploy deu certo?#

Teste de fora do servidor, com o endereço público:

bash
curl -i https://meuapp.exemplo.com/health

Você espera HTTP/2 200 e o JSON {"status":"ok"}. No Django, crie uma view simples em /health ou abra a página inicial. Se a resposta for outra, leia os logs do processo: o traceback do Python mostra a linha exata.

Quais são os erros mais comuns?#

A conexão é recusada ou expira. O servidor escuta em 127.0.0.1 ou numa porta diferente da exposta. Passe --host 0.0.0.0 (Uvicorn) ou -b 0.0.0.0:PORTA (Gunicorn).

O Django responde Bad Request (400). O domínio não está em ALLOWED_HOSTS. Com DEBUG = False e a lista sem o domínio (ou vazia), o Django responde 400 a todas as requisições.

Formulário do Django falha com 403 (CSRF verification failed). A origem do site não está em CSRF_TRUSTED_ORIGINS, ou o Django não sabe que a conexão original era HTTPS. Confira a lista e o cabeçalho do proxy.

O CSS do admin do Django não carrega. Faltou o collectstatic ou o WhiteNoise ficou fora do MIDDLEWARE. Rode o collectstatic a cada deploy.

KeyError: 'SECRET_KEY'. A variável de ambiente não foi definida no servidor. A falha é proposital: melhor não subir do que subir sem segredo. Veja o que são variáveis de ambiente.

ModuleNotFoundError. Uma dependência ficou fora do requirements.txt. Gere o arquivo com pip freeze > requirements.txt dentro do ambiente virtual do projeto.

O app reinicia sozinho ou fica lento. Muitos workers para pouca RAM: cada um é um processo com a sua cópia do app. Reduza --workers ou aumente a memória.

O que mais cuidar antes de abrir ao público?#

  • Banco de dados: um arquivo SQLite local ao lado do app é frágil quando há vários workers e quando o disco do servidor é trocado. Para uso real, prefira PostgreSQL ou MySQL; veja como hospedar um banco de dados.
  • Migrações: rode migrate (Django) ou o seu sistema de migração a cada deploy, antes de liberar o tráfego novo.
  • CORS: no FastAPI, use o CORSMiddleware com origens exatas; em Flask e Django, os pacotes flask-cors e django-cors-headers.
  • Domínio próprio: aponte o domínio para o app e atualize ALLOWED_HOSTS junto.

Como fazer isso na Square Cloud#

Os comandos de produção deste guia viram o comando de início da aplicação na Square Cloud. Você envia o projeto por ZIP no painel, pela CLI, pelo GitHub ou pela extensão do VS Code; no start, a plataforma roda pip install -r requirements.txt e define a variável PORT. O arquivo squarecloud.app é opcional: o painel o gera no deploy. No campo de comando de início do formulário, use o servidor do seu framework:

  • FastAPI: uvicorn main:app --host 0.0.0.0 --port $PORT --workers 2
  • Flask: gunicorn -w 2 -b "0.0.0.0:$PORT" 'app:app'
  • Django: python manage.py collectstatic --noinput && python manage.py migrate && gunicorn meusite.wsgi -b "0.0.0.0:$PORT"

Como não há etapa de build, o Django reúne os estáticos e aplica as migrações ali mesmo, a cada início. Todo plano dá à aplicação web um subdomínio *.squareweb.app com TLS automático, e é esse host que entra nas variáveis do Django: ALLOWED_HOSTS=meuapp.squareweb.app e CSRF_TRUSTED_ORIGINS=https://meuapp.squareweb.app, ao lado do SECRET_KEY. As variáveis ficam no painel, já existem durante a instalação e valem no próximo restart, sem rebuild.

Cada worker ocupa memória, e uma aplicação web usa no mínimo 512 MB da RAM do plano; o Hobby começa em R$ 12,49 por mês com 1 GB. Se os workers estourarem a memória, a aplicação para até você aumentar a RAM ou reduzir os workers. O reinício automático, ligado por padrão no deploy pelo painel, cobre só os processos que encerram com erro. O disco persiste entre deploys, mas o SQLite continua frágil com vários workers: a hospedagem de PostgreSQL e MySQL começa no Standard (a partir de R$ 44,99 por mês), no mesmo datacenter das aplicações.

Veja os planos em hospedagem de APIs e o runtime em Python.

Hospedagem de APIs

APIs em containers sempre ligados, sem cold start

Saiba mais

Perguntas frequentes

Posso usar o servidor de desenvolvimento do Flask ou o runserver do Django em produção?

Não. Eles servem para desenvolver, não para carga real nem para segurança. Em produção, use Gunicorn (Flask e Django) ou Uvicorn (FastAPI).

Quantos workers devo usar?

Comece com poucos e ajuste pelo uso real. A documentação do Flask sugere partir de CPU * 2 no Gunicorn, mas cada worker é um processo com a sua própria memória, então o limite de RAM também conta.

Por que o Django responde 400 (Bad Request) depois do deploy?

Com DEBUG desligado, o Django só aceita os domínios listados em ALLOWED_HOSTS. Adicione à lista o host que aparece na URL.

Preciso do Nginx na frente do Gunicorn?

Num servidor próprio (VPS), a documentação do Flask recomenda um proxy reverso na frente do Gunicorn. Em plataformas que já entregam HTTPS e roteamento, essa camada já vem pronta.

Próximo artigoComo hospedar um script ou API em PythonPara hospedar um script ou API em Python, fixe a versão, isole dependências em um venv, leia segredos do ambiente e trate o SIGTERM. Passo a passo com código.