Por que “funciona no meu notebook” não basta
Existe um ponto em que todo projeto de machine learning deixa de ser um experimento e precisa virar um serviço. É o momento em que o modelo que você treinou — e que responde perfeitamente no Swagger UI do seu laptop — precisa sobreviver a um desconhecido digitando uma URL do outro lado do mundo. Se ele morre quando a tela do notebook dorme, ele ainda não é um produto. É um brinquedo que só você pode usar.
Essa é exatamente a transição que este guia percorre. O ponto de partida é uma API FastAPI de previsão de churn (a probabilidade de um cliente cancelar): você envia os dados de um cliente e recebe de volta uma probabilidade, uma previsão e um nível de risco. O modelo usa um pipeline do scikit-learn (escalador + classificador) já treinado. E o caminho até “alguém consegue chamá-lo” passa por containerização com Docker, um servidor na AWS e três falhas que ninguém avisa.
Em 2026, com a maturidade do MLOps, o básico mudou: entregar código não é mais suficiente — você entrega um ambiente. A boa notícia é que o custo de fazer isso direito caiu drasticamente. Uma instância de nível gratuito é tudo o que este projeto precisa.
O que você ganha (e o que não ganha)
✅ O que você ganha
- Um modelo acessível por qualquer pessoa com o endereço IP, não só por você no seu computador.
- Reprodutibilidade total: as mesmas versões de Python e de pacotes rodam no seu laptop e no servidor.
- Um endereço estável (Elastic IP) que não muda quando a máquina reinicia.
- Entendimento real dos erros de infraestrutura — que são a maioria dos problemas reais de deploy.
- Base pronta para evoluir para um app de machine learning completo na nuvem.
⚠️ O que você NÃO ganha
- HTTPS: tudo roda em HTTP simples na porta 8000, aceitável para aprendizado, insuficiente para dados reais de clientes.
- Autenticação: qualquer pessoa com o IP consegue chamar o endpoint — sem API key, sem rate limiting.
- Auto-recuperação: se a AWS reiniciar o hardware, a API fica fora do ar até você reiniciá-la manualmente.
Requisitos
| Componente | Mínimo | Recomendado | Ideal |
|---|---|---|---|
| Modelo | Pipeline scikit-learn já treinado | Salvo com joblib/pickle | Versionado com metadados de treino |
| Servidor | Qualquer máquina com Docker | EC2 t3.micro (Ubuntu) | Free tier + Elastic IP |
| Conhecimento | Python básico | FastAPI + linha de comando | Noções de rede e grupos de segurança |
| Tempo estimado | 2 horas | 1 hora | 45 minutos |
Passo 1 — Congele o ambiente antes de qualquer coisa
O erro mais comum em deploy é deixar o pip instalar “o que for mais recente” dentro do container. O resultado: o modelo funciona no seu laptop e quebra no servidor, porque as versões divergiram. A solução é congelar o ambiente com pip freeze, que lista as versões exatas dos pacotes que você realmente está usando:
fastapi==0.141.1
uvicorn[standard]==0.40.0
scikit-learn==1.9.0
pandas==3.0.3
numpy==2.4.6
pydantic==2.13.4
joblib==1.5.3Um detalhe que passa despercebido: se você roda Python 3.14 localmente (uma versão recente), não use a imagem base python:3.11-slim por hábito. Use a imagem que espelha seu ambiente — python:3.14-slim — para o container se comportar o mais próximo possível da sua máquina.
Passo 2 — Escreva o Dockerfile (e o erro que quebrou tudo)
FROM python:3.14-slim
WORKDIR /code
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY ./app ./app
COPY ./models ./models
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]O build em si passou, mas na primeira execução veio o erro que ninguém espera: ModuleNotFoundError: No module named 'schemas'. A causa é sutil. Localmente você sempre rodava uvicorn main:app de dentro da pasta app/, então o schemas.py ficava ao lado e era encontrado. No Dockerfile, o CMD roda uvicorn app.main:app a partir de /code, o que faz o Python tratar app como um pacote — e o diretório de trabalho diferente é o suficiente para quebrar a importação.
A correção é simples: diga ao Uvicorn para carregar o app usando a pasta app como raiz, imitando como você já rodava antes:
CMD ["uvicorn", "main:app", "--app-dir", "app", "--host", "0.0.0.0", "--port", "8000"]Verificação intermediária: reconstrua a imagem, suba o container e envie uma requisição de teste. A resposta deve trazer exatamente a mesma probabilidade, previsão e nível de risco da Parte A — se bateu, o container não alterou o comportamento do modelo.
Passo 3 — Suba a instância EC2
O guia usa uma instância t3.micro com Ubuntu, elegível para o nível gratuito da AWS — ideal para uma API FastAPI com um único modelo. Dois pontos de configuração fazem diferença:
- Grupo de segurança: crie duas regras de entrada — SSH na porta 22 (restrita ao seu IP) e uma regra TCP personalizada na porta 8000 (aberta a todos, pois é onde a API será exposta).
- Usuário IAM em vez de root: operar direto da conta root não tem limites de acesso; um usuário IAM faz o mesmo trabalho com um raio de dano muito menor se algo der errado.
Passo 4 — Instale o Docker e envie o projeto
Depois de acessar a instância via SSH, instale o Docker:
sudo apt update
sudo apt install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker $USEREm vez de subir a imagem para o Amazon ECR, o guia copia os arquivos do projeto via scp e reconstrói a imagem direto na instância — mais simples para um projeto pequeno. Foi aí que apareceu a segunda falha:
ssh: connect to host <ip> port 22: Connection timed outO instinto é culpar a instância. O problema real era a regra de firewall do grupo de segurança, que restringia o SSH ao seu IP — e o seu IP havia mudado desde a configuração. Atualizar a regra resolveu na hora. É um tropeço comum e rápido de diagnosticar, mas que trava o deploy se você não souber onde olhar.
Passo 5 — Rode o container em produção
Com os arquivos transferidos, reconstrua a imagem (o mesmo comando que você rodou localmente) e suba o container:
docker build -t churn-api .
docker run -d -p 8000:8000 churn-apiO -d é o que importa aqui: ele mantém o container rodando em segundo plano e desacoplado da sua sessão SSH. Sem ele, o container morre no instante em que você desconecta — o que destruiria todo o propósito. Teste acessando http://<ip-público>:8000/docs no seu navegador e envie o mesmo cliente de teste pelo /predict. Se os valores baterem com os locais, alguém em qualquer lugar do mundo agora consegue chamar o seu modelo.
Passo 6 — Prenda o endereço com um Elastic IP
O IP público padrão do EC2 não é garantido: ele muda quando a instância para e reinicia. Anexar um Elastic IP fixa o endereço, garantindo que ele permaneça o mesmo mesmo após reiniciar o servidor. É um passo rápido que transforma o deploy de “prova de conceito” em algo minimamente estável.
O que ainda falta para produção de verdade
O guia é honesto sobre o que não resolveu: não há HTTPS (tudo em HTTP simples), não há autenticação (qualquer um com o IP pode chamar /predict) e não há reinício automático do container se a instância reiniciar. Nenhum desses problemas é difícil — mas deixá-los de fora, em vez de fingir completude, é o que separa um guia confiável de um artigo enganoso.
Troubleshooting
- ❌
ModuleNotFoundError: No module named 'schemas'dentro do container → O diretório de trabalho mudou (o app é tratado como pacote). Solução: useCMD ["uvicorn", "main:app", "--app-dir", "app", ...]. - ❌
ssh: connect to host port 22: Connection timed out→ A regra de SSH do grupo de segurança restringe ao seu IP antigo. Solução: atualize o IP de origem na regra da porta 22. - ❌ O container morre ao fechar a sessão SSH → Faltou o
-dnodocker run. Solução: rode comdocker run -d -p 8000:8000. - ❌ O IP público mudou e a API ficou inacessível → O IP padrão do EC2 não é fixo. Solução: anexe um Elastic IP.
- ❌ O build no servidor é muito mais lento que no laptop → A t3.micro tem menos CPU para compilar pacotes. Solução: aceite o tempo maior ou use imagens pré-construídas (wheels) sempre que possível.
- ❌ Versões de pacote divergem entre local e servidor → Você não congelou o ambiente. Solução: gere o
requirements.txtcompip freezeantes de escrever o Dockerfile.
Perguntas frequentes
Preciso de um servidor pago para isso? Não. Uma instância t3.micro é elegível para o nível gratuito da AWS nos primeiros 12 meses, o suficiente para uma API de um único modelo.
Por que Docker e não só copiar o código? Porque o código carrega dependências implícitas — versão de Python, caminhos de arquivos, bibliotecas. O Docker empacota tudo isso num ambiente autossuficiente que roda em qualquer máquina.
Por que o modelo quebrou se o build não deu erro? O build só verifica se as dependências instalaram, não se o app carrega corretamente no diretório de trabalho do container. Erros de importação só aparecem na execução.
O endpoint já está seguro? Não. Falta HTTPS e autenticação — sem isso, qualquer pessoa com o IP consegue chamá-lo. Aceitável para aprendizado, não para dados reais.
O que é Elastic IP e por que preciso dele? É um endereço fixo da AWS. O IP público padrão muda a cada parada/reinício; o Elastic IP o mantém estável.
Para onde isso vai
O percurso — notebook → API local → API containerizada → serviço acessível pela internet — encapsula a diferença fundamental entre escrever código e colocar machine learning em produção. A lição mais valiosa não é técnica: a maioria dos problemas de deploy não está na lógica do modelo, mas em incompatibilidades de ambiente e mudanças sutis de infraestrutura. O próximo passo natural é aplicar essa mesma visão a um modelo maior que um serviço de endpoint único — e é exatamente aí que o que você aprendeu aqui vira base.
Descubra mais sobre noticiAI
Assine para receber nossas notícias mais recentes por e-mail.



