Umbrel
EN

Como rodar IA no Docker com GPU no Linux: Guia definitivo

Aprenda a rodar IA local com GPU no Docker Engine no Linux sem depender do Docker Desktop. Configure o Docker Compose com NVIDIA e escaneie com Trivy.

Áudio Narrado Ouça este artigo
00:00 / 00:00

A Docker dedicou boa parte do seu tempo no palco da WeAreDevelopers para exibir integrações corporativas: plugins proprietários de IA, catálogos fechados de containers e painéis pagos de análise de vulnerabilidades. Para fornecedores e equipes que já pagam assinaturas do Docker Desktop, esse foco comercial faz sentido.

Para quem mantém servidores Linux headless, racks de homelab ou pequenos clusters, a história é bem diferente.

Você não precisa de extensões empacotadas para desktop para ter um ambiente confiável de inferência local. Nem precisa assinar o Docker Hub para auditar a segurança das suas imagens. Quando tiramos o marketing do caminho, sobram apenas o Docker Engine, o Compose v2 e utilitários leves de linha de comando em código aberto. A seguir, veja como rodar inteligência artificial local com alto desempenho e fazer varreduras de vulnerabilidade diretamente no seu Linux.

Docker Engine vs Docker Desktop: diferenças no homelab

O Docker Desktop envelopa uma máquina virtual, uma interface gráfica e uma loja de extensões em volta das ferramentas padrão de containers. No macOS e no Windows, essa camada de VM é necessária, já que esses sistemas não têm recursos nativos do kernel Linux como cgroups e namespaces.

No Linux, essa arquitetura não faz sentido. Ela cria uma barreira desnecessária de virtualização, desperdiça memória RAM e roda daemons de suporte em segundo plano sem qualquer utilidade real.

text +-----------------------------------------------------------+ | Docker Desktop (Linux/Mac/Win) | | [ Interface Electron ] -> [ VMs Auxiliares ] -> [ Daemons]| | Consumo em repouso: ~1,8 GB a 3,5 GB de RAM | +-----------------------------------------------------------+ vs +-----------------------------------------------------------+ | Docker Engine Community Edition (Linux Nativo) | | [ systemd dockerd ] -> [ containerd ] -> [ runc ] | | Consumo em repouso: ~45 MB de RAM | +-----------------------------------------------------------+

Quando você roda inteligência artificial local no homelab, a memória RAM do sistema e a largura de banda do barramento PCIe são valiosas. Um modelo quantizado de 8 bilhões de parâmetros (8B) precisa de cerca de 5,5 GB de VRAM só para carregar na placa de vídeo, fora vários gigabytes de RAM do host para cache de contexto e prompts. Gastar 2 GB de RAM com uma interface em Electron é um desperdício em hardware headless.

Recurso Docker Engine (CE 27.x) Docker Desktop para Linux
Uso de RAM em repouso ~45 MB 1,8 GB – 3,5 GB
Pass-through de GPU Nativo via NVIDIA CDI Camada de ponte virtualizada
Integração com o host Serviço direto no systemd VM isolada / User namespace
Modelo de licença Código aberto (Apache 2.0) Comercial / Planos por assinatura
Auditoria de segurança Ferramentas CLI (Trivy/Syft) Docker Scout / Interface comercial
Uso ideal Homelabs, servidores headless, CI/CD Máquinas de desenvolvimento mistas

Se o seu host roda Debian 12 ou Ubuntu 24.04 bare metal, esqueça o instalador do Desktop. Instale o Docker Engine nativo direto dos repositórios apt oficiais da Docker e configure o acesso ao hardware sem intermediários.

Acesso à GPU sem extensões: NVIDIA CDI e Compose

Muitas apresentações corporativas vendem a ideia de que o instalador com "um clique" do Docker Desktop é o único caminho para aprendizado de máquina local. Nos bastidores, rodar IA no Docker com GPU no Linux nativo exige apenas duas coisas: os drivers oficiais da NVIDIA instalados no host e o NVIDIA Container Toolkit configurado com a Container Device Interface (CDI).

Por anos, repassar uma placa de vídeo NVIDIA para um container exigia editar o /etc/docker/daemon.json, injetar um runtime customizado (nvidia-container-runtime) e passar flags manuais como --gpus all no terminal. Essa abordagem quebrava com frequência. Atualizações do Docker mudavam caminhos do runtime, e o Docker Compose lidava de forma instável com a reserva de hardware.

O CDI resolve esse problema. Criado pelo Container Orchestrated Device Workgroup, o CDI adota uma especificação independente de fornecedor que descreve dispositivos do host usando um arquivo YAML simples. Runtimes como containerd e runc leem esse arquivo diretamente. Eles configuram nós de dispositivo (/dev/nvidia*), permissões Unix e bibliotecas de driver sem exigir wrappers mirabolantes nem alterações manuais no daemon.

1. Gerar a especificação CDI

Instale o pacote nvidia-container-toolkit usando o gerenciador de pacotes da sua distribuição. Verifique se os módulos de kernel da NVIDIA (nvidia e nvidia_uvm) estão ativos:

# Verifica se a GPU está visível e a versão do driver no host
nvidia-smi

Em seguida, gere o arquivo de especificação CDI. Essa ferramenta inspeciona o hardware de vídeo e grava as definições em /etc/cdi/nvidia.yaml:

# Gera a especificação CDI
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml

# Confirma se o Docker e o Container Toolkit reconhecem os dispositivos
nvidia-ctk cdi list

O comando deve listar nomes como nvidia.com/gpu=all e índices individuais como nvidia.com/gpu=0. Agora o Docker consegue referenciar suas placas físicas diretamente por esses identificadores.

2. Configurar NVIDIA no Docker Compose passo a passo

O arquivo Compose a seguir faz o deploy de um backend de inferência com Ollama integrado à interface Open-WebUI. Ele reserva a GPU usando o driver CDI e aplica limites claros de hardware para evitar sobrecarga no servidor:

services:
  ollama:
    image: ollama/ollama:0.5.7
    container_name: ollama-inference
    restart: unless-stopped
    environment:
      # Mantém o modelo na VRAM por 24h para evitar lentidão de recarga
      - OLLAMA_KEEP_ALIVE=24h
      # Limita requisições paralelas conforme a capacidade da placa
      - OLLAMA_NUM_PARALLEL=2
    volumes:
      # Armazena os pesos dos modelos no armazenamento do host
      - /opt/ollama/data:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: cdi
              device_ids:
                - nvidia.com/gpu=all
        limits:
          cpus: '4.0'
          memory: 14G
    # Evita que buffers de inferência usem memória swap
    mem_swappiness: 0

  webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: unless-stopped
    ports:
      - "3000:8080"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
    volumes:
      - /opt/open-webui/data:/app/backend/data
    depends_on:
      - ollama
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G

Essa configuração com CDI ignora completamente os hooks legados de runtime. Ela entrega os nós de dispositivo diretamente para o container, garantindo zero overhead de virtualização entre o CUDA e o hardware físico.

Controlando contextos gigantes: limites de memória no Docker Compose

Modelos de linguagem locais trazem uma dor de cabeça operacional que quase nunca vemos em aplicações web convencionais: o inchaço descontrolado de contexto.

Quando você carrega um modelo 8B quantizado em 4 bits, ele ocupa confortavelmente cerca de 5 GB de VRAM. Tudo funciona bem até que alguém cola um log imenso ou um documento inteiro no chat. Quando a janela de contexto pula de 2.048 tokens para 32.768 tokens, o cache de chaves e valores (KV cache) do modelo cresce absurdamente.

[ Contexto: 2.048 tokens ]  -> KV Cache: ~250 MB  -> Cabe na VRAM
[ Contexto: 32.768 tokens ] -> KV Cache: ~4,2 GB  -> Transborda da VRAM para a RAM

Quando uma placa NVIDIA esgota a VRAM, o motor de inferência (como llama.cpp ou vLLM) despeja os dados excedentes na memória RAM do sistema. Se a RAM do host também lotar, o infame OOM killer (Out-Of-Memory) do kernel Linux entra em ação.

Sem limites explícitos de memória no container, o OOM killer escolhe alvos usando uma heurística própria (oom_badness). Em vez de derrubar o container que exagerou no prompt, ele frequentemente encerra o processo pai dockerd, a sua conexão SSH ou o banco de dados do sistema.

Para blindar o servidor contra travamentos durante prompts longos, defina limites de memória diretamente no arquivo Compose:

deploy:
  resources:
    limits:
      memory: 14G
    reservations:
      memory: 8G

Três ajustes mantêm o host seguro:

  1. mem_swappiness: 0: Por padrão, o Linux move páginas inativas de memória para a partição swap em disco. Se um modelo despejar pesos ou camadas de contexto no swap — mesmo em um NVMe veloz —, a velocidade de geração despenca de 40 tokens por segundo para frações de token por segundo. Isso gera um pico de I/O wait capaz de congelar os discos do sistema. Ajustar mem_swappiness: 0 força o kernel a descartar caches de páginas em vez de empurrar o container para o swap.
  2. Tetos rígidos de memória (limits.memory): Configure esse teto sempre abaixo da memória física total da máquina. Em um servidor com 16 GB de RAM, limite o container de inferência a 12 GB ou 14 GB. Se um prompt imenso estourar esse limite, o container falha internamente ou reinicia sozinho, mantendo o sistema operacional intacto.
  3. oom_score_adj: Em servidores headless, ajuste o peso do container no OOM killer:
# Adicione à definição do serviço no Compose
oom_score_adj: 500

O kernel Linux pontua processos para sobrevivência de -1000 (imune, reservado a serviços essenciais) até 1000 (primeiro da fila para morrer). Processos normais ficam em 0. Atribuir 500 garante que, sob estresse extremo de memória, o kernel finalize o motor de inferência antes de tocar em serviços críticos do host.

Varredura de vulnerabilidades aberta em containers: Trivy e Syft

Apresentações corporativas adoram destacar scanners de vulnerabilidade em nuvem atrelados a planos pagos. Essas ferramentas obrigam você a enviar as imagens para um registro externo antes de analisar vulnerabilidades conhecidas (CVEs).

Dá para fazer varreduras completas de segurança localmente, sem mandar nenhuma imagem privada para servidores de terceiros. Com utilitários de linha de comando abertos como Trivy (mantido pela Aqua Security) e Syft (da Anchore), você inspeciona imagens, confere versões de pacotes e gera um SBOM (Software Bill of Materials) completo direto no terminal.

O fluxo se divide em duas etapas simples:

  • O Syft cataloga cada binário, biblioteca Python e pacote do sistema operacional embutido nas camadas do container, exportando relatórios estruturados em formatos padrão como SPDX ou CycloneDX.
  • O Trivy cruza essa lista de componentes com bases públicas de vulnerabilidades (incluindo o NVD e trackers de segurança de distribuições), apontando pacotes desatualizados por ordem de severidade.

Veja um script shell para auditar qualquer imagem construída localmente:

#!/usr/bin/env bash
set -euo pipefail

# Nome da imagem alvo e diretório de saída
IMAGE_TAG="local/custom-ai-worker:latest"
REPORT_DIR="/var/log/container-audits"

mkdir -p "${REPORT_DIR}"

echo "==> Passo 1: Catalogando pacotes com Syft (SBOM)..."
# Gera documento padronizado SPDX em JSON listando componentes instalados
syft packages "${IMAGE_TAG}" -o spdx-json > "${REPORT_DIR}/sbom.json"

echo "==> Passo 2: Verificando vulnerabilidades nas camadas com Trivy..."
# Analisa a imagem localmente, ignorando falhas sem correção e focando no que importa
trivy image \
  --severity HIGH,CRITICAL \
  --ignore-unfixed \
  --exit-code 1 \
  --format table \
  --output "${REPORT_DIR}/scan-report.txt" \
  "${IMAGE_TAG}" || EXIT_STATUS=$?

if [ "${EXIT_STATUS:-0}" -ne 0 ]; then
  echo "[ALERTA] Vulnerabilidades de segurança encontradas em ${IMAGE_TAG}."
  echo "Confira o relatório detalhado em: ${REPORT_DIR}/scan-report.txt"
  exit 1
fi

echo "Varredura concluída: Nenhuma falha HIGH ou CRITICAL pendente de correção."

Esse processo local leva poucos segundos, não exige login, elimina limites de taxa de download de registros e mantém suas imagens internas em sigilo absoluto.

+-----------------------+      +-----------------------+
|  Imagem do Container  | ---> |  Syft: Gera o SBOM    |
|  (Engine Local)       |      |  (Pacotes e Deps)     |
+-----------------------+      +-----------------------+
            |                              |
            v                              v
+------------------------------------------------------+
|  Trivy: Cruza os pacotes com bancos de dados de CVE  |
|  Saída: Tabelas de texto puro ou relatórios em JSON  |
+------------------------------------------------------+

Armadilhas comuns para evitar

  • Usar flags obsoletas como --gpus: Deploys modernos gerenciam acesso a hardware via reservas de dispositivo no Compose v2 e CDI. Evite scripts legados que exigem mexer no daemon.json para carregar o antigo nvidia-container-runtime.
  • Deixar limites de contexto sem configuração: Definir memória no Docker resolve só metade do problema. Se o backend de inferência (como Ollama, llama.cpp ou vLLM) não tiver um teto de num_ctx configurado, um cliente pode requisitar contextos gigantescos que esgotam a VRAM e a RAM de uma vez. Defina limites claros nos parâmetros do serviço ou no Modelfile.
  • Salvar modelos dentro das camadas do container: Nunca permita que os arquivos de pesos sejam gravados na camada overlay2 do container. Se ficarem na camada efêmera, qualquer atualização de imagem apagará seus downloads ou lotará a partição raiz. Monte um volume persistente do host para armazenar os modelos.
  • Operar GPUs de desktop sem modo de persistência ativado: Placas voltadas para jogos (como RTX 3080 ou 4090) entram em modos de economia de energia agressivos quando os containers encerram. Quando chega uma nova requisição de inferência, o pico repentino de consumo pode desestabilizar fontes de alimentação intermediárias. Ative o modo de persistência no host executando nvidia-smi -pm 1 para manter o driver pronto e estável entre execuções.
  • Expor portas de modelos sem autenticação: Por padrão, o Ollama e ferramentas parecidas não exigem autenticação de API. Nunca exponha a porta 11434 diretamente para a rede externa. Mantenha o serviço dentro de uma rede interna do Docker e gerencie o acesso usando um reverse proxy (como Traefik, Caddy ou Nginx) com controle de acesso adequado.

Perguntas frequentes

Como alocar recursos de GPU para containers Docker usando o Compose v2?

Adicione um bloco de reservations dentro de deploy.resources no seu arquivo Compose. Usando o NVIDIA Container Toolkit com a interface CDI, você indica o driver cdi e define o identificador nvidia.com/gpu=all ou o ID específico da placa (como nvidia.com/gpu=0). Isso concede acesso direto sem depender de flags legadas ou camadas gráficas.

É possível fazer varredura de segurança em containers sem assinar o Docker Hub?

Sim. Ferramentas de linha de comando como Trivy e Syft analisam as imagens localmente na máquina. Elas cruzam as camadas do container com repositórios públicos de CVEs e geram relatórios SBOM em formatos como SPDX e CycloneDX, sem depender de contas pagas ou conexões com a nuvem.

Qual é a diferença entre as extensões do Docker Desktop e rodar containers no Docker Engine?

As extensões do Docker Desktop rodam dentro de um painel em Electron e dependem de uma máquina virtual no Linux. O Docker Engine nativo fala diretamente com o kernel do host, sem intermediários. Ele consome cerca de 45 MB de RAM ocioso contra vários gigabytes do Desktop, aproveitando melhor os recursos de placa de vídeo, rede e armazenamento.

Como evitar travamentos por falta de memória (OOM) ao rodar containers de IA no Docker?

Combine limites rígidos de memória (limits.memory no Compose) com a desativação de swap (mem_swappiness: 0). Isso impede que o container sobrecarregue o disco com paginação desnecessária, protege o host contra o esgotamento de RAM e instrui o kernel a finalizar apenas o processo de inferência problemático, mantendo o servidor no ar.

O NVIDIA CDI funciona com múltiplos modelos de GPU no Docker Compose?

Sim. Você pode direcionar placas específicas pelos identificadores criados no CDI. Rode nvidia-ctk cdi list no terminal para identificar os nomes disponíveis. No Compose, troque nvidia.com/gpu=all pelo identificador desejado, como nvidia.com/gpu=0 ou nvidia.com/gpu=1, isolando cada container na sua própria placa física.

Artigos relacionados neste cluster

  • /en/posts/cursor-xai-vs-local-llms-privacy-guide-for-homelabs/
  • /en/posts/docker-cloud-sandboxes-secure-ai-agents-without-vendor-lock-in/
  • /en/posts/docker-sandbox-kit-securing-ai-agent-container-isolation/

Comece limpando as camadas desnecessárias do seu servidor. Instale o Docker Engine nativo, gere seu arquivo CDI em poucos segundos e suba seu primeiro modelo local com limites claros de recursos. Você ganha desempenho, economiza memória preciosa para os seus modelos e mantém o controle total do seu homelab.

Relacionados neste cluster

  • /pt/posts/como-isolar-agentes-ia-docker-sandbox/
  • /pt/posts/como-isolar-agentes-ia-docker/
  • /pt/posts/como-isolar-agentes-ia-sem-docker-cloud/