Artigo

Docker para Projetos de IA em Python: do Ambiente ao Deploy

Docker para Projetos de IA em Python: do Ambiente ao Deploy
Tempo de leitura8 minutos

Se você já ouviu “na minha máquina funciona”, sabe o problema que o Docker resolve. Em projetos de IA, isso fica ainda mais crítico: versões de CUDA, PyTorch, dependências compiladas e modelos pesados fazem o ambiente quebrar com facilidade. Neste guia mostro, na prática, como empacotar um projeto de IA em Python com Docker — de uma imagem enxuta até um deploy reproduzível com GPU.

Vou partir de um caso real: uma API que carrega um modelo e responde inferências. O mesmo padrão serve para scripts de treino, pipelines de dados e notebooks convertidos em jobs.

Por que Docker é indispensável em projetos de IA

  • Reprodutibilidade: a mesma imagem roda igual no seu PC, no servidor e na nuvem.
  • Isolamento: PyTorch, TensorFlow e CUDA não brigam mais entre projetos.
  • Deploy previsível: o que passou no teste é exatamente o que vai para produção.
  • Colaboração: um colega sobe o ambiente com um único comando.

Estrutura do projeto

Organize assim antes de escrever o Dockerfile:

projeto/
  app/
    main.py
    modelos.py
  requirements.txt
  Dockerfile
  .dockerignore
  docker-compose.yml

O Dockerfile base

Comece com uma imagem oficial do Python e instale apenas o necessário. A ordem das camadas importa: dependências primeiro, código depois — assim o cache é reaproveitado.

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

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

COPY app/ ./app/

EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Dois detalhes fazem diferença: PYTHONUNBUFFERED garante que os logs apareçam em tempo real, e --no-cache-dir evita inchar a imagem com caches do pip.

O .dockerignore importa mais do que parece

Sem ele, você copia gigabytes de dados, modelos e venv para dentro da imagem. Um bom .dockerignore:

.venv/
__pycache__/
*.pyc
.git/
data/
modelos/
*.ipynb
.ipynb_checkpoints/

Isso reduz o tamanho da imagem e o tempo de build drasticamente.

Variáveis de ambiente e segredos

Nunca coloque chaves de API na imagem. Passe por variáveis de ambiente em tempo de execução.

docker run -p 8000:8000 \
  -e OPENAI_API_KEY=sk-... \
  minha-api-ia

Em produção, use um gerenciador de segredos ou um arquivo .env carregado pelo orquestrador — nunca comite o .env.

Montando volumes para modelos e dados

Modelos são grandes e não devem ficar dentro da imagem. Monte-os como volume:

docker run -v $(pwd)/modelos:/app/modelos minha-api-ia

Assim você troca o modelo sem reconstruir a imagem.

docker-compose para desenvolvimento

Quando o projeto tem API + banco + cache, o compose simplifica a vida:

services:
  api:
    build: .
    ports:
      - "8000:8000"
    env_file: .env
    volumes:
      - ./modelos:/app/modelos
    depends_on:
      - redis
  redis:
    image: redis:7-alpine
docker compose up --build

Imagens com GPU (CUDA)

Para treinar ou inferir com GPU, a imagem base muda. Use as imagens oficiais com CUDA e instale o PyTorch compatível.

FROM nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04

RUN apt-get update && apt-get install -y python3 python3-pip && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ ./app/
CMD ["python", "-m", "app.main"]

No host, é preciso ter o NVIDIA Container Toolkit. Rode com --gpus all:

docker run --gpus all minha-api-ia

Reduzindo o tamanho da imagem

  • Use slim ou alpine quando possível.
  • Combine comandos RUN para reduzir camadas.
  • Remova caches e arquivos temporários no mesmo RUN.
  • Considere multi-stage build para separar build de runtime.
# Multi-stage: compila em uma etapa, roda em outra
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.12-slim
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
COPY app/ ./app/
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]

Boas práticas de segurança

  • Rode como usuário não-root dentro do container.
  • Não embuta segredos na imagem.
  • Fixe versões das dependências.
  • Escaneie a imagem em busca de vulnerabilidades.
RUN useradd -m appuser
USER appuser

Erros comuns

  • Copiar o .venv para dentro da imagem — inútil e pesado.
  • Instalar dependências depois do código — quebra o cache a cada mudança.
  • Rodar como root sem necessidade.
  • Guardar modelos dentro da imagem.
  • Esquecer PYTHONUNBUFFERED e não ver logs.

Checklist de deploy

  1. Build reproduzível com versões fixadas.
  2. Segredos fora da imagem.
  3. Modelos e dados como volumes.
  4. Usuário não-root.
  5. Healthcheck configurado.
  6. Logs em tempo real.

Multi-stage build na prática

O multi-stage build separa o que é necessário para construir do que é necessário para rodar. Isso reduz o tamanho final porque ferramentas de compilação não vão para a imagem de produção. Em projetos de IA, onde dependências como PyTorch são enormes, essa diferença pode chegar a centenas de megabytes.

O fluxo é: um estágio instala tudo em um diretório isolado, e o estágio final copia apenas o resultado. Assim, compiladores e caches ficam para trás.

FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.12-slim
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
COPY app/ ./app/
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]

Camadas e cache: por que a ordem importa

Docker constrói a imagem em camadas e reaproveita camadas inalteradas. Se você copia o código antes de instalar dependências, qualquer mudança em um arquivo invalida a instalação inteira — e cada build reinstala tudo. A regra de ouro é: copie primeiro o que muda pouco (requirements.txt), instale, e só então copie o código que muda sempre.

Esse detalhe, isolado, costuma reduzir o tempo de build de minutos para segundos no dia a dia.

Saúde do container com healthcheck

Um container “de pé” não significa que a aplicação responde. O healthcheck permite que o orquestrador reinicie o serviço quando ele travar.

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

Combine com um endpoint /health que verifica dependências críticas, como o carregamento do modelo.

Logs e observabilidade

Dentro do container, escreva logs para a saída padrão — nunca para arquivos internos. O orquestrador coleta o stdout. Com PYTHONUNBUFFERED=1, os logs aparecem em tempo real, o que é essencial para depurar problemas em produção.

Versionando e publicando a imagem

Nunca use apenas a tag latest em produção. Tagueie com a versão ou o hash do commit, para saber exatamente qual código está rodando e poder voltar atrás.

docker build -t minha-api-ia:1.4.2 .
docker tag minha-api-ia:1.4.2 registry/minha-api-ia:1.4.2
docker push registry/minha-api-ia:1.4.2

Testando o container antes do deploy

Antes de publicar, valide a imagem como se fosse produção. Suba o container, faça uma requisição e confira se a resposta está correta. Um teste simples evita descobrir o problema só depois do deploy.

docker run -d --name teste -p 8000:8000 minha-api-ia:1.4.2
curl -s http://localhost:8000/health
docker logs teste
docker rm -f teste

Se o /health responde e o log não mostra erro, a imagem está saudável.

Integração com o host e permissões

Ao montar volumes, o container precisa de permissão de leitura no host. Um erro comum é o usuário não-root dentro do container não conseguir ler o modelo montado. Resolva alinhando o UID ou ajustando as permissões do diretório.

# descobre o uid do usuario do container
docker run --rm minha-api-ia id -u
# ajusta o dono no host
sudo chown -R 1000:1000 ./modelos

Variáveis de ambiente em produção

Não use --env com segredos em scripts versionados. Prefira arquivos .env lidos pelo orquestrador, ou um cofre de segredos. Mantenha um .env.example no repositório com as chaves (sem valores) para documentar o que é necessário.

# .env.example (versionado, sem segredos)
OPENAI_API_KEY=
MODEL_PATH=/app/modelos
LOG_LEVEL=INFO

Otimizando o tamanho com .dockerignore refinado

Um erro silencioso é enviar contexto de build grande demais. O .dockerignore reduz o que o daemon recebe, acelerando o build mesmo antes de qualquer RUN. Em projetos de IA, dados e checkpoints são os maiores vilões.

data/
checkpoints/
*.ckpt
*.pt
*.onnx
*.h5
runs/
mlruns/
wandb/
.venv/
.git/

Uma imagem de IA bem-feita raramente passa de poucas centenas de megabytes — o modelo vive fora dela, como volume.

Comparando estratégias de imagem

Abordagem Tamanho Velocidade de build Indicada para
Single-stage slim Pequeno Média APIs simples
Multi-stage Muito pequeno Média Produção
CUDA runtime Grande Lenta Inferência com GPU

Automatizando o build com Makefile

Padronize os comandos do dia a dia em um Makefile. Isso evita que cada pessoa do time use uma flag diferente.

build:
	docker build -t minha-api-ia:local .

run:
	docker run --rm -p 8000:8000 --env-file .env minha-api-ia:local

test:
	docker run --rm minha-api-ia:local pytest -q

push:
	docker tag minha-api-ia:local registry/minha-api-ia:latest
	docker push registry/minha-api-ia:latest

Conclusão e próximos passos

Comece com um Dockerfile simples, valide localmente e só depois adicione GPU, multi-stage e compose. Cada refinamento deve resolver um problema real — não adicione complexidade por antecipação.

Resumo

Docker transforma “funciona na minha máquina” em “funciona em qualquer lugar”. Para projetos de IA, isso significa versões de CUDA e bibliotecas sob controle, modelos montados como volume e deploy previsível. Comece com um Dockerfile simples, adicione GPU só quando precisar e mantenha segredos fora da imagem. O resto é refinamento.

Anterior e próximo

Leituras recentes

Temas parecidos para continuar navegando no blog.

Conteúdo criado para leitura limpa, boa hierarquia visual e navegação clara.