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
slimoualpinequando possível. - Combine comandos
RUNpara 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
.venvpara 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
PYTHONUNBUFFEREDe não ver logs.
Checklist de deploy
- Build reproduzível com versões fixadas.
- Segredos fora da imagem.
- Modelos e dados como volumes.
- Usuário não-root.
- Healthcheck configurado.
- 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.
